Merge "Move TaskThumbnailViewData initialisation to init." into main
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 7dd40af..502c001 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -84,8 +84,6 @@
     private static final String TAG = "TaskbarStashController";
     private static final boolean DEBUG = false;
 
-    private static boolean sEnableSoftwareImeForTests = false;
-
     /**
      * Def. value for @param shouldBubblesFollow in
      * {@link #updateAndAnimateTransientTaskbar(boolean)} */
@@ -1172,13 +1170,13 @@
         }
 
         // Do not stash if pinned taskbar, hardware keyboard is attached and no IME is docked
-        if (isHardwareKeyboard() && DisplayController.isPinnedTaskbar(mActivity)
+        if (mActivity.isHardwareKeyboard() && DisplayController.isPinnedTaskbar(mActivity)
                 && !mActivity.isImeDocked()) {
             return false;
         }
 
         // Do not stash if hardware keyboard is attached, in 3 button nav and desktop windowing mode
-        if (isHardwareKeyboard()
+        if (mActivity.isHardwareKeyboard()
                 && mActivity.isThreeButtonNav()
                 && mControllers.taskbarDesktopModeController
                     .getAreDesktopTasksVisibleAndNotInOverview()) {
@@ -1193,21 +1191,6 @@
         return mIsImeShowing || mIsImeSwitcherShowing;
     }
 
-    private boolean isHardwareKeyboard() {
-        return mActivity.isHardwareKeyboard() && !sEnableSoftwareImeForTests;
-    }
-
-    /**
-     * Overrides {@link #isHardwareKeyboard()} to {@code false} for testing, if enabled.
-     * <p>
-     * Virtual devices are sometimes in hardware keyboard mode, leading to an inconsistent
-     * testing environment.
-     */
-    @VisibleForTesting
-    static void enableSoftwareImeForTests(boolean enable) {
-        sEnableSoftwareImeForTests = enable;
-    }
-
     /**
      * Updates the proper flag to indicate whether the task bar should be stashed.
      *
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index 9683f8b..b63cf02 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -339,13 +339,15 @@
             // clear restored state
             mBubbleBarViewController.removeAllBubbles();
             mBubbles.clear();
+            mBubbleBarViewController.showOverflow(update.showOverflow);
         }
 
         BubbleBarBubble bubbleToSelect = null;
 
         if (Flags.enableOptionalBubbleOverflow()
                 && update.showOverflowChanged && !update.showOverflow && update.addedBubble != null
-                && update.removedBubbles.isEmpty()) {
+                && update.removedBubbles.isEmpty()
+                && !mBubbles.isEmpty()) {
             // A bubble was added from the overflow (& now it's empty / not showing)
             mBubbles.put(update.addedBubble.getKey(), update.addedBubble);
             mBubbleBarViewController.removeOverflowAndAddBubble(update.addedBubble);
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index 37c6194..0d0feff 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -729,7 +729,8 @@
             Runnable onEndRunnable) {
         FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams((int) mIconSize, (int) mIconSize,
                 Gravity.LEFT);
-        boolean isOverflowSelected = mSelectedBubbleView.isOverflow();
+        boolean isOverflowSelected =
+                mSelectedBubbleView != null && mSelectedBubbleView.isOverflow();
         boolean removingOverflow = removedBubble.isOverflow();
         boolean addingOverflow = addedBubble.isOverflow();
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index d00959e..569dd56 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -1142,6 +1142,7 @@
 
     /** Removes all existing bubble views */
     public void removeAllBubbles() {
+        mOverflowAdded = false;
         mBarView.removeAllViews();
     }
 
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 85e2b6e..08d43c0 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -257,7 +257,7 @@
         mRecentTasksChangedListener = null;
     }
 
-    private void initRunningTasks(ArrayList<RunningTaskInfo> runningTasks) {
+    private void initRunningTasks(List<RunningTaskInfo> runningTasks) {
         // Tasks are retrieved in order of most recently launched/used to least recently launched.
         mRunningTasks = new ArrayList<>(runningTasks);
         Collections.reverse(mRunningTasks);
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
deleted file mode 100644
index 6a25ecb..0000000
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ /dev/null
@@ -1,1660 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE;
-
-import static com.android.launcher3.Flags.enableUnfoldStateAnimation;
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-import static com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
-import static com.android.quickstep.util.LogUtils.splitFailureMessage;
-
-import android.app.ActivityManager;
-import android.app.ActivityOptions;
-import android.app.PendingIntent;
-import android.app.PictureInPictureParams;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ShortcutInfo;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.IBinder.DeathRecipient;
-import android.os.Message;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.Log;
-import android.view.IRemoteAnimationRunner;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.window.DesktopModeFlags;
-import android.window.IOnBackInvokedCallback;
-import android.window.RemoteTransition;
-import android.window.TaskSnapshot;
-import android.window.TransitionFilter;
-
-import androidx.annotation.MainThread;
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-import androidx.annotation.WorkerThread;
-
-import com.android.internal.logging.InstanceId;
-import com.android.internal.util.ScreenshotRequest;
-import com.android.internal.view.AppearanceRegion;
-import com.android.launcher3.dagger.ApplicationContext;
-import com.android.launcher3.dagger.LauncherAppSingleton;
-import com.android.launcher3.util.DaggerSingletonObject;
-import com.android.launcher3.util.Preconditions;
-import com.android.quickstep.dagger.QuickstepBaseAppComponent;
-import com.android.quickstep.util.ActiveGestureProtoLogProxy;
-import com.android.quickstep.util.ContextualSearchInvoker;
-import com.android.quickstep.util.unfold.ProxyUnfoldTransitionProvider;
-import com.android.systemui.shared.recents.ISystemUiProxy;
-import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
-import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
-import com.android.systemui.shared.system.RecentsAnimationListener;
-import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController;
-import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController;
-import com.android.systemui.shared.system.smartspace.SmartspaceState;
-import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig;
-import com.android.systemui.unfold.progress.IUnfoldAnimation;
-import com.android.systemui.unfold.progress.IUnfoldTransitionListener;
-import com.android.wm.shell.back.IBackAnimation;
-import com.android.wm.shell.bubbles.IBubbles;
-import com.android.wm.shell.bubbles.IBubblesListener;
-import com.android.wm.shell.common.pip.IPip;
-import com.android.wm.shell.common.pip.IPipAnimationListener;
-import com.android.wm.shell.desktopmode.IDesktopMode;
-import com.android.wm.shell.desktopmode.IDesktopTaskListener;
-import com.android.wm.shell.draganddrop.IDragAndDrop;
-import com.android.wm.shell.onehanded.IOneHanded;
-import com.android.wm.shell.recents.IRecentTasks;
-import com.android.wm.shell.recents.IRecentTasksListener;
-import com.android.wm.shell.recents.IRecentsAnimationController;
-import com.android.wm.shell.recents.IRecentsAnimationRunner;
-import com.android.wm.shell.shared.GroupedTaskInfo;
-import com.android.wm.shell.shared.IShellTransitions;
-import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
-import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
-import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
-import com.android.wm.shell.shared.split.SplitBounds;
-import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition;
-import com.android.wm.shell.splitscreen.ISplitScreen;
-import com.android.wm.shell.splitscreen.ISplitScreenListener;
-import com.android.wm.shell.splitscreen.ISplitSelectListener;
-import com.android.wm.shell.startingsurface.IStartingWindow;
-import com.android.wm.shell.startingsurface.IStartingWindowListener;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.LinkedHashMap;
-import java.util.List;
-
-import javax.inject.Inject;
-
-/**
- * Holds the reference to SystemUI.
- */
-@LauncherAppSingleton
-public class SystemUiProxy implements ISystemUiProxy, NavHandle {
-    private static final String TAG = "SystemUiProxy";
-
-    public static final DaggerSingletonObject<SystemUiProxy> INSTANCE =
-            new DaggerSingletonObject<>(QuickstepBaseAppComponent::getSystemUiProxy);
-
-    private static final int MSG_SET_SHELF_HEIGHT = 1;
-    private static final int MSG_SET_LAUNCHER_KEEP_CLEAR_AREA_HEIGHT = 2;
-
-    private ISystemUiProxy mSystemUiProxy;
-    private IPip mPip;
-    private IBubbles mBubbles;
-    private ISysuiUnlockAnimationController mSysuiUnlockAnimationController;
-    private ISplitScreen mSplitScreen;
-    private IOneHanded mOneHanded;
-    private IShellTransitions mShellTransitions;
-    private IStartingWindow mStartingWindow;
-    private IRecentTasks mRecentTasks;
-    private IBackAnimation mBackAnimation;
-    private IDesktopMode mDesktopMode;
-    private IUnfoldAnimation mUnfoldAnimation;
-    private final DeathRecipient mSystemUiProxyDeathRecipient = () -> {
-        MAIN_EXECUTOR.execute(() -> clearProxy());
-    };
-
-    // Save the listeners passed into the proxy since OverviewProxyService may not have been bound
-    // yet, and we'll need to set/register these listeners with SysUI when they do.  Note that it is
-    // up to the caller to clear the listeners to prevent leaks as these can be held indefinitely
-    // in case SysUI needs to rebind.
-    private IPipAnimationListener mPipAnimationListener;
-    private IBubblesListener mBubblesListener;
-    private ISplitScreenListener mSplitScreenListener;
-    private ISplitSelectListener mSplitSelectListener;
-    private IStartingWindowListener mStartingWindowListener;
-    private ILauncherUnlockAnimationController mLauncherUnlockAnimationController;
-    private String mLauncherActivityClass;
-    private IRecentTasksListener mRecentTasksListener;
-    private IUnfoldTransitionListener mUnfoldAnimationListener;
-    private IDesktopTaskListener mDesktopTaskListener;
-    private final LinkedHashMap<RemoteTransition, TransitionFilter> mRemoteTransitions =
-            new LinkedHashMap<>();
-
-    private final List<Runnable> mStateChangeCallbacks = new ArrayList<>();
-
-    private IBinder mOriginalTransactionToken = null;
-    private IOnBackInvokedCallback mBackToLauncherCallback;
-    private IRemoteAnimationRunner mBackToLauncherRunner;
-    private IDragAndDrop mDragAndDrop;
-    private final HomeVisibilityState mHomeVisibilityState = new HomeVisibilityState();
-    private final FocusState mFocusState = new FocusState();
-
-    // Used to dedupe calls to SystemUI
-    private int mLastShelfHeight;
-    private boolean mLastShelfVisible;
-
-    // Used to dedupe calls to SystemUI
-    private int mLastLauncherKeepClearAreaHeight;
-    private boolean mLastLauncherKeepClearAreaHeightVisible;
-
-    private final Context mContext;
-    private final Handler mAsyncHandler;
-
-    // TODO(141886704): Find a way to remove this
-    @SystemUiStateFlags
-    private long mLastSystemUiStateFlags;
-
-    /**
-     * This is a singleton pending intent that is used to start recents via Shell (which is a
-     * different process). It is bare-bones, so it's expected that the component and options will
-     * be provided via fill-in intent.
-     */
-    private final PendingIntent mRecentsPendingIntent;
-
-    @Nullable
-    private final ProxyUnfoldTransitionProvider mUnfoldTransitionProvider;
-
-    @Inject
-    public SystemUiProxy(@ApplicationContext Context context) {
-        mContext = context;
-        mAsyncHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), this::handleMessageAsync);
-        final Intent baseIntent = new Intent().setPackage(mContext.getPackageName());
-        final ActivityOptions options = ActivityOptions.makeBasic()
-                .setPendingIntentCreatorBackgroundActivityStartMode(
-                        ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
-        mRecentsPendingIntent = PendingIntent.getActivity(mContext, 0, baseIntent,
-                PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT
-                        | Intent.FILL_IN_COMPONENT, options.toBundle());
-
-        mUnfoldTransitionProvider =
-                (enableUnfoldStateAnimation() && new ResourceUnfoldTransitionConfig().isEnabled())
-                         ? new ProxyUnfoldTransitionProvider() : null;
-    }
-
-    @Override
-    public void onBackEvent(KeyEvent backEvent) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.onBackEvent(backEvent);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call onBackPressed", e);
-            }
-        }
-    }
-
-    @Override
-    public void onImeSwitcherPressed() {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.onImeSwitcherPressed();
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call onImeSwitcherPressed", e);
-            }
-        }
-    }
-
-    @Override
-    public void onImeSwitcherLongPress() {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.onImeSwitcherLongPress();
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call onImeSwitcherLongPress");
-            }
-        }
-    }
-
-    @Override
-    public void updateContextualEduStats(boolean isTrackpadGesture, String gestureType) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.updateContextualEduStats(isTrackpadGesture, gestureType);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call updateContextualEduStats");
-            }
-        }
-    }
-
-    @Override
-    public void setHomeRotationEnabled(boolean enabled) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.setHomeRotationEnabled(enabled);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call onBackPressed", e);
-            }
-        }
-    }
-
-    @Override
-    public IBinder asBinder() {
-        // Do nothing
-        return null;
-    }
-
-    /**
-     * Sets proxy state, including death linkage, various listeners, and other configuration objects
-     */
-    @MainThread
-    public void setProxy(ISystemUiProxy proxy, IPip pip, IBubbles bubbles, ISplitScreen splitScreen,
-            IOneHanded oneHanded, IShellTransitions shellTransitions,
-            IStartingWindow startingWindow, IRecentTasks recentTasks,
-            ISysuiUnlockAnimationController sysuiUnlockAnimationController,
-            IBackAnimation backAnimation, IDesktopMode desktopMode,
-            IUnfoldAnimation unfoldAnimation, IDragAndDrop dragAndDrop) {
-        Preconditions.assertUIThread();
-        unlinkToDeath();
-        mSystemUiProxy = proxy;
-        mPip = pip;
-        mBubbles = bubbles;
-        mSplitScreen = splitScreen;
-        mOneHanded = oneHanded;
-        mShellTransitions = shellTransitions;
-        mStartingWindow = startingWindow;
-        mSysuiUnlockAnimationController = sysuiUnlockAnimationController;
-        mRecentTasks = recentTasks;
-        mBackAnimation = backAnimation;
-        mDesktopMode = desktopMode;
-        mUnfoldAnimation = enableUnfoldStateAnimation() ? null : unfoldAnimation;
-        mDragAndDrop = dragAndDrop;
-        linkToDeath();
-        // re-attach the listeners once missing due to setProxy has not been initialized yet.
-        setPipAnimationListener(mPipAnimationListener);
-        setBubblesListener(mBubblesListener);
-        registerSplitScreenListener(mSplitScreenListener);
-        registerSplitSelectListener(mSplitSelectListener);
-        mHomeVisibilityState.init(mShellTransitions);
-        mFocusState.init(mShellTransitions);
-        setStartingWindowListener(mStartingWindowListener);
-        setLauncherUnlockAnimationController(
-                mLauncherActivityClass, mLauncherUnlockAnimationController);
-        new LinkedHashMap<>(mRemoteTransitions).forEach(this::registerRemoteTransition);
-        setupTransactionQueue();
-        registerRecentTasksListener(mRecentTasksListener);
-        setBackToLauncherCallback(mBackToLauncherCallback, mBackToLauncherRunner);
-        setUnfoldAnimationListener(mUnfoldAnimationListener);
-        setDesktopTaskListener(mDesktopTaskListener);
-        setAssistantOverridesRequested(new ContextualSearchInvoker(mContext)
-                .getSysUiAssistOverrideInvocationTypes());
-        mStateChangeCallbacks.forEach(Runnable::run);
-
-        if (mUnfoldTransitionProvider != null) {
-            if (unfoldAnimation != null) {
-                try {
-                    unfoldAnimation.setListener(mUnfoldTransitionProvider);
-                    mUnfoldTransitionProvider.setActive(true);
-                } catch (RemoteException e) {
-                    // Ignore
-                }
-            } else {
-                mUnfoldTransitionProvider.setActive(false);
-            }
-        }
-    }
-
-    /**
-     * Clear the proxy to release held resources and turn the majority of its operations into no-ops
-     */
-    @MainThread
-    public void clearProxy() {
-        setProxy(null, null, null, null, null, null, null, null, null, null, null, null, null);
-    }
-
-    /**
-     * Adds a callback to be notified whenever the active state changes
-     */
-    public void addOnStateChangeListener(Runnable callback) {
-        mStateChangeCallbacks.add(callback);
-    }
-
-    /**
-     * Removes a previously added state change callback
-     */
-    public void removeOnStateChangeListener(Runnable callback) {
-        mStateChangeCallbacks.remove(callback);
-    }
-
-    // TODO(141886704): Find a way to remove this
-    public void setLastSystemUiStateFlags(@SystemUiStateFlags long stateFlags) {
-        mLastSystemUiStateFlags = stateFlags;
-    }
-
-    // TODO(141886704): Find a way to remove this
-    @SystemUiStateFlags
-    public long getLastSystemUiStateFlags() {
-        return mLastSystemUiStateFlags;
-    }
-
-    public boolean isActive() {
-        return mSystemUiProxy != null;
-    }
-
-    private void linkToDeath() {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.asBinder().linkToDeath(mSystemUiProxyDeathRecipient, 0 /* flags */);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Failed to link sysui proxy death recipient");
-            }
-        }
-    }
-
-    private void unlinkToDeath() {
-        if (mSystemUiProxy != null) {
-            mSystemUiProxy.asBinder().unlinkToDeath(mSystemUiProxyDeathRecipient, 0 /* flags */);
-        }
-    }
-
-    @Override
-    public void startScreenPinning(int taskId) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.startScreenPinning(taskId);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call startScreenPinning", e);
-            }
-        }
-    }
-
-    @Override
-    public void onOverviewShown(boolean fromHome) {
-        onOverviewShown(fromHome, TAG);
-    }
-
-    public void onOverviewShown(boolean fromHome, String tag) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.onOverviewShown(fromHome);
-            } catch (RemoteException e) {
-                Log.w(tag, "Failed call onOverviewShown from: " + (fromHome ? "home" : "app"), e);
-            }
-        }
-    }
-
-    @MainThread
-    @Override
-    public void onStatusBarTouchEvent(MotionEvent event) {
-        Preconditions.assertUIThread();
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.onStatusBarTouchEvent(event);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call onStatusBarTouchEvent with arg: " + event, e);
-            }
-        }
-    }
-
-    @Override
-    public void onStatusBarTrackpadEvent(MotionEvent event) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.onStatusBarTrackpadEvent(event);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call onStatusBarTrackpadEvent with arg: " + event, e);
-            }
-        }
-    }
-
-    @Override
-    public void onAssistantProgress(float progress) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.onAssistantProgress(progress);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call onAssistantProgress with progress: " + progress, e);
-            }
-        }
-    }
-
-    @Override
-    public void onAssistantGestureCompletion(float velocity) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.onAssistantGestureCompletion(velocity);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call onAssistantGestureCompletion", e);
-            }
-        }
-    }
-
-    @Override
-    public void startAssistant(Bundle args) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.startAssistant(args);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call startAssistant", e);
-            }
-        }
-    }
-
-    @Override
-    public void setAssistantOverridesRequested(int[] invocationTypes) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.setAssistantOverridesRequested(invocationTypes);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call setAssistantOverridesRequested", e);
-            }
-        }
-    }
-
-    @Override
-    public void animateNavBarLongPress(boolean isTouchDown, boolean shrink, long durationMs) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.animateNavBarLongPress(isTouchDown, shrink, durationMs);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call animateNavBarLongPress", e);
-            }
-        }
-    }
-
-    @Override
-    public void setOverrideHomeButtonLongPress(long duration, float slopMultiplier,
-            boolean haptic) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.setOverrideHomeButtonLongPress(duration, slopMultiplier, haptic);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call setOverrideHomeButtonLongPress", e);
-            }
-        }
-    }
-
-    @Override
-    public void notifyAccessibilityButtonClicked(int displayId) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.notifyAccessibilityButtonClicked(displayId);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call notifyAccessibilityButtonClicked", e);
-            }
-        }
-    }
-
-    @Override
-    public void notifyAccessibilityButtonLongClicked() {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.notifyAccessibilityButtonLongClicked();
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call notifyAccessibilityButtonLongClicked", e);
-            }
-        }
-    }
-
-    @Override
-    public void stopScreenPinning() {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.stopScreenPinning();
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call stopScreenPinning", e);
-            }
-        }
-    }
-
-    @Override
-    public void notifyPrioritizedRotation(int rotation) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.notifyPrioritizedRotation(rotation);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call notifyPrioritizedRotation with arg: " + rotation, e);
-            }
-        }
-    }
-
-    @Override
-    public void notifyTaskbarStatus(boolean visible, boolean stashed) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.notifyTaskbarStatus(visible, stashed);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call notifyTaskbarStatus with arg: " +
-                        visible + ", " + stashed, e);
-            }
-        }
-    }
-
-    /**
-     * NOTE: If called to suspend, caller MUST call this method to also un-suspend
-     * @param suspend should be true to stop auto-hide, false to resume normal behavior
-     */
-    @Override
-    public void notifyTaskbarAutohideSuspend(boolean suspend) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.notifyTaskbarAutohideSuspend(suspend);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call notifyTaskbarAutohideSuspend with arg: " +
-                        suspend, e);
-            }
-        }
-    }
-
-    @Override
-    public void takeScreenshot(ScreenshotRequest request) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.takeScreenshot(request);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call takeScreenshot");
-            }
-        }
-    }
-
-    @Override
-    public void expandNotificationPanel() {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.expandNotificationPanel();
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call expandNotificationPanel", e);
-            }
-        }
-    }
-
-    @Override
-    public void toggleNotificationPanel() {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.toggleNotificationPanel();
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call toggleNotificationPanel", e);
-            }
-        }
-    }
-
-    @Override
-    public void toggleQuickSettingsPanel() {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.toggleQuickSettingsPanel();
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call toggleQuickSettingsPanel", e);
-            }
-        }
-    }
-
-    //
-    // Pip
-    //
-
-    /**
-     * Sets the shelf height.
-     */
-    public void setShelfHeight(boolean visible, int shelfHeight) {
-        Message.obtain(mAsyncHandler, MSG_SET_SHELF_HEIGHT,
-                visible ? 1 : 0 , shelfHeight).sendToTarget();
-    }
-
-    @WorkerThread
-    private void setShelfHeightAsync(int visibleInt, int shelfHeight) {
-        boolean visible = visibleInt != 0;
-        boolean changed = visible != mLastShelfVisible || shelfHeight != mLastShelfHeight;
-        IPip pip = mPip;
-        if (pip != null && changed) {
-            mLastShelfVisible = visible;
-            mLastShelfHeight = shelfHeight;
-            try {
-                pip.setShelfHeight(visible, shelfHeight);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call setShelfHeight visible: " + visible
-                        + " height: " + shelfHeight, e);
-            }
-        }
-    }
-
-    /**
-     * Sets the height of the keep clear area that is going to be reported by
-     * the Launcher for the Hotseat.
-     */
-    public void setLauncherKeepClearAreaHeight(boolean visible, int height) {
-        Message.obtain(mAsyncHandler, MSG_SET_LAUNCHER_KEEP_CLEAR_AREA_HEIGHT,
-                visible ? 1 : 0 , height).sendToTarget();
-    }
-
-    @WorkerThread
-    private void setLauncherKeepClearAreaHeight(int visibleInt, int height) {
-        boolean visible = visibleInt != 0;
-        boolean changed = visible != mLastLauncherKeepClearAreaHeightVisible
-                || height != mLastLauncherKeepClearAreaHeight;
-        IPip pip = mPip;
-        if (pip != null && changed) {
-            mLastLauncherKeepClearAreaHeightVisible = visible;
-            mLastLauncherKeepClearAreaHeight = height;
-            try {
-                pip.setLauncherKeepClearAreaHeight(visible, height);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call setLauncherKeepClearAreaHeight visible: " + visible
-                        + " height: " + height, e);
-            }
-        }
-    }
-
-    /**
-     * Sets listener to get pip animation callbacks.
-     */
-    public void setPipAnimationListener(IPipAnimationListener listener) {
-        if (mPip != null) {
-            try {
-                mPip.setPipAnimationListener(listener);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call setPinnedStackAnimationListener", e);
-            }
-        }
-        mPipAnimationListener = listener;
-    }
-
-    /**
-     * @return Destination bounds of auto-pip animation, {@code null} if the animation is not ready.
-     */
-    @Nullable
-    public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
-            PictureInPictureParams pictureInPictureParams, int launcherRotation,
-            Rect hotseatKeepClearArea) {
-        if (mPip != null) {
-            try {
-                return mPip.startSwipePipToHome(componentName, activityInfo,
-                        pictureInPictureParams, launcherRotation, hotseatKeepClearArea);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call startSwipePipToHome", e);
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Notifies WM Shell that launcher has finished the preparation of the animation for swipe to
-     * home. WM Shell can choose to fade out the overlay when entering PIP is finished, and WM Shell
-     * should be responsible for cleaning up the overlay.
-     */
-    public void stopSwipePipToHome(int taskId, ComponentName componentName, Rect destinationBounds,
-            SurfaceControl overlay, Rect appBounds, Rect sourceRectHint) {
-        if (mPip != null) {
-            try {
-                mPip.stopSwipePipToHome(taskId, componentName, destinationBounds, overlay,
-                        appBounds, sourceRectHint);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call stopSwipePipToHome");
-            }
-        }
-    }
-
-    /**
-     * Notifies WM Shell that launcher has aborted all the animation for swipe to home. WM Shell
-     * can use this callback to clean up its internal states.
-     */
-    public void abortSwipePipToHome(int taskId, ComponentName componentName) {
-        if (mPip != null) {
-            try {
-                mPip.abortSwipePipToHome(taskId, componentName);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call abortSwipePipToHome");
-            }
-        }
-    }
-
-    /**
-     * Sets the next pip animation type to be the alpha animation.
-     */
-    public void setPipAnimationTypeToAlpha() {
-        if (mPip != null) {
-            try {
-                mPip.setPipAnimationTypeToAlpha();
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call setPipAnimationTypeToAlpha", e);
-            }
-        }
-    }
-
-    /**
-     * Sets the app icon size in pixel used by Launcher all apps.
-     */
-    public void setLauncherAppIconSize(int iconSizePx) {
-        if (mPip != null) {
-            try {
-                mPip.setLauncherAppIconSize(iconSizePx);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call setLauncherAppIconSize", e);
-            }
-        }
-    }
-
-    //
-    // Bubbles
-    //
-
-    /**
-     * Sets the listener to be notified of bubble state changes.
-     */
-    public void setBubblesListener(IBubblesListener listener) {
-        if (mBubbles != null) {
-            try {
-                if (mBubblesListener != null) {
-                    // Clear out any previous listener
-                    mBubbles.unregisterBubbleListener(mBubblesListener);
-                }
-                if (listener != null) {
-                    mBubbles.registerBubbleListener(listener);
-                }
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call registerBubblesListener");
-            }
-        }
-        mBubblesListener = listener;
-    }
-
-    /**
-     * Tells SysUI to show the bubble with the provided key.
-     * @param key the key of the bubble to show.
-     * @param top top coordinate of bubble bar on screen
-     */
-    public void showBubble(String key, int top) {
-        if (mBubbles != null) {
-            try {
-                mBubbles.showBubble(key, top);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call showBubble");
-            }
-        }
-    }
-
-    /**
-     * Tells SysUI to remove all bubbles.
-     */
-    public void removeAllBubbles() {
-        if (mBubbles == null) return;
-        try {
-            mBubbles.removeAllBubbles();
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed call removeAllBubbles");
-        }
-    }
-
-    /**
-     * Tells SysUI to collapse the bubbles.
-     */
-    public void collapseBubbles() {
-        if (mBubbles != null) {
-            try {
-                mBubbles.collapseBubbles();
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call collapseBubbles");
-            }
-        }
-    }
-
-    /**
-     * Tells SysUI when the bubble is being dragged.
-     * Should be called only when the bubble bar is expanded.
-     * @param bubbleKey key of the bubble being dragged
-     */
-    public void startBubbleDrag(@Nullable String bubbleKey) {
-        if (mBubbles == null) return;
-        try {
-            mBubbles.startBubbleDrag(bubbleKey);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed call startBubbleDrag");
-        }
-    }
-
-    /**
-     * Tells SysUI when the bubble stops being dragged.
-     * Should be called only when the bubble bar is expanded.
-     *
-     * @param location location of the bubble bar
-     * @param top      new top coordinate for bubble bar on screen
-     */
-    public void stopBubbleDrag(BubbleBarLocation location, int top) {
-        if (mBubbles == null) return;
-        try {
-            mBubbles.stopBubbleDrag(location, top);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed call stopBubbleDrag");
-        }
-    }
-
-    /**
-     * Tells SysUI to dismiss the bubble with the provided key.
-     *
-     * @param key the key of the bubble to dismiss.
-     * @param timestamp the timestamp when the removal happened.
-     */
-    public void dragBubbleToDismiss(String key, long timestamp) {
-        if (mBubbles == null) return;
-        try {
-            mBubbles.dragBubbleToDismiss(key, timestamp);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed call dragBubbleToDismiss");
-        }
-    }
-
-    /**
-     * Tells SysUI to show user education relative to the reference point provided.
-     * @param position the bubble bar top center position in Screen coordinates.
-     */
-    public void showUserEducation(Point position) {
-        try {
-            mBubbles.showUserEducation(position.x, position.y);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed call showUserEducation");
-        }
-    }
-
-    /**
-     * Tells SysUI to update the bubble bar location to the new location.
-     * @param location new location for the bubble bar
-     * @param source what triggered the location update
-     */
-    public void setBubbleBarLocation(BubbleBarLocation location,
-            @BubbleBarLocation.UpdateSource int source) {
-        try {
-            mBubbles.setBubbleBarLocation(location, source);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed call setBubbleBarLocation");
-        }
-    }
-
-    /**
-     * Tells SysUI the top coordinate of bubble bar on screen
-     *
-     * @param topOnScreen top coordinate for bubble bar on screen
-     */
-    public void updateBubbleBarTopOnScreen(int topOnScreen) {
-        try {
-            if (mBubbles != null) {
-                mBubbles.updateBubbleBarTopOnScreen(topOnScreen);
-            }
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed call updateBubbleBarTopOnScreen");
-        }
-    }
-
-    /**
-     * Tells SysUI to show a shortcut bubble.
-     *
-     * @param info the shortcut info used to create or identify the bubble.
-     */
-    public void showShortcutBubble(ShortcutInfo info) {
-        try {
-            if (mBubbles != null) {
-                mBubbles.showShortcutBubble(info);
-            }
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed call show bubble for shortcut");
-        }
-    }
-
-    /**
-     * Tells SysUI to show a bubble of an app.
-     *
-     * @param intent the intent used to create the bubble.
-     */
-    public void showAppBubble(Intent intent) {
-        try {
-            if (mBubbles != null) {
-                mBubbles.showAppBubble(intent);
-            }
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed call show bubble for app");
-        }
-    }
-
-    /** Tells SysUI to show the expanded view. */
-    public void showExpandedView() {
-        try {
-            if (mBubbles != null) {
-                mBubbles.showExpandedView();
-            }
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to call showExpandedView");
-        }
-    }
-
-    //
-    // Splitscreen
-    //
-
-    public void registerSplitScreenListener(ISplitScreenListener listener) {
-        if (mSplitScreen != null) {
-            try {
-                mSplitScreen.registerSplitScreenListener(listener);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call registerSplitScreenListener");
-            }
-        }
-        mSplitScreenListener = listener;
-    }
-
-    public void unregisterSplitScreenListener(ISplitScreenListener listener) {
-        if (mSplitScreen != null) {
-            try {
-                mSplitScreen.unregisterSplitScreenListener(listener);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call unregisterSplitScreenListener");
-            }
-        }
-        mSplitScreenListener = null;
-    }
-
-    public void registerSplitSelectListener(ISplitSelectListener listener) {
-        if (mSplitScreen != null) {
-            try {
-                mSplitScreen.registerSplitSelectListener(listener);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call registerSplitSelectListener");
-            }
-        }
-        mSplitSelectListener = listener;
-    }
-
-    public void unregisterSplitSelectListener(ISplitSelectListener listener) {
-        if (mSplitScreen != null) {
-            try {
-                mSplitScreen.unregisterSplitSelectListener(listener);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call unregisterSplitSelectListener");
-            }
-        }
-        mSplitSelectListener = null;
-    }
-
-    /** Start multiple tasks in split-screen simultaneously. */
-    public void startTasks(int taskId1, Bundle options1, int taskId2, Bundle options2,
-            @StagePosition int splitPosition, @PersistentSnapPosition int snapPosition,
-            RemoteTransition remoteTransition, InstanceId instanceId) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSplitScreen.startTasks(taskId1, options1, taskId2, options2, splitPosition,
-                        snapPosition, remoteTransition, instanceId);
-            } catch (RemoteException e) {
-                Log.w(TAG, splitFailureMessage("startTasks", "RemoteException"), e);
-            }
-        }
-    }
-
-    public void startIntentAndTask(PendingIntent pendingIntent, int userId1, Bundle options1,
-            int taskId, Bundle options2, @StagePosition int splitPosition,
-            @PersistentSnapPosition int snapPosition, RemoteTransition remoteTransition,
-            InstanceId instanceId) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSplitScreen.startIntentAndTask(pendingIntent, userId1, options1, taskId, options2,
-                        splitPosition, snapPosition, remoteTransition, instanceId);
-            } catch (RemoteException e) {
-                Log.w(TAG, splitFailureMessage("startIntentAndTask", "RemoteException"), e);
-            }
-        }
-    }
-
-    public void startIntents(PendingIntent pendingIntent1, int userId1,
-            @Nullable ShortcutInfo shortcutInfo1, Bundle options1, PendingIntent pendingIntent2,
-            int userId2, @Nullable ShortcutInfo shortcutInfo2, Bundle options2,
-            @StagePosition int splitPosition, @PersistentSnapPosition int snapPosition,
-            RemoteTransition remoteTransition, InstanceId instanceId) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSplitScreen.startIntents(pendingIntent1, userId1, shortcutInfo1, options1,
-                        pendingIntent2, userId2, shortcutInfo2, options2, splitPosition,
-                        snapPosition, remoteTransition, instanceId);
-            } catch (RemoteException e) {
-                Log.w(TAG, splitFailureMessage("startIntents", "RemoteException"), e);
-            }
-        }
-    }
-
-    public void startShortcutAndTask(ShortcutInfo shortcutInfo, Bundle options1, int taskId,
-            Bundle options2, @StagePosition int splitPosition,
-            @PersistentSnapPosition int snapPosition, RemoteTransition remoteTransition,
-            InstanceId instanceId) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSplitScreen.startShortcutAndTask(shortcutInfo, options1, taskId, options2,
-                        splitPosition, snapPosition, remoteTransition, instanceId);
-            } catch (RemoteException e) {
-                Log.w(TAG, splitFailureMessage("startShortcutAndTask", "RemoteException"), e);
-            }
-        }
-    }
-
-    public void startShortcut(String packageName, String shortcutId, int position,
-            Bundle options, UserHandle user, InstanceId instanceId) {
-        if (mSplitScreen != null) {
-            try {
-                mSplitScreen.startShortcut(packageName, shortcutId, position, options,
-                        user, instanceId);
-            } catch (RemoteException e) {
-                Log.w(TAG, splitFailureMessage("startShortcut", "RemoteException"), e);
-            }
-        }
-    }
-
-    public void startIntent(PendingIntent intent, int userId, Intent fillInIntent, int position,
-            Bundle options, InstanceId instanceId) {
-        if (mSplitScreen != null) {
-            try {
-                mSplitScreen.startIntent(intent, userId, fillInIntent, position, options,
-                        instanceId);
-            } catch (RemoteException e) {
-                Log.w(TAG, splitFailureMessage("startIntent", "RemoteException"), e);
-            }
-        }
-    }
-
-    //
-    // One handed
-    //
-
-    public void startOneHandedMode() {
-        if (mOneHanded != null) {
-            try {
-                mOneHanded.startOneHanded();
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call startOneHandedMode", e);
-            }
-        }
-    }
-
-    public void stopOneHandedMode() {
-        if (mOneHanded != null) {
-            try {
-                mOneHanded.stopOneHanded();
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call stopOneHandedMode", e);
-            }
-        }
-    }
-
-    //
-    // Remote transitions
-    //
-
-    public void registerRemoteTransition(
-            RemoteTransition remoteTransition, TransitionFilter filter) {
-        if (mShellTransitions != null) {
-            try {
-                mShellTransitions.registerRemote(filter, remoteTransition);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call registerRemoteTransition");
-            }
-        }
-        if (!mRemoteTransitions.containsKey(remoteTransition)) {
-            mRemoteTransitions.put(remoteTransition, filter);
-        }
-    }
-
-    public void unregisterRemoteTransition(RemoteTransition remoteTransition) {
-        if (mShellTransitions != null) {
-            try {
-                mShellTransitions.unregisterRemote(remoteTransition);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call registerRemoteTransition");
-            }
-        }
-        mRemoteTransitions.remove(remoteTransition);
-    }
-
-    public HomeVisibilityState getHomeVisibilityState() {
-        return mHomeVisibilityState;
-    }
-
-    public FocusState getFocusState() {
-        return mFocusState;
-    }
-
-    /**
-     * Returns a surface which can be used to attach overlays to home task or null if
-     * the task doesn't exist or sysui is not connected
-     */
-    @Nullable
-    public SurfaceControl getHomeTaskOverlayContainer() {
-        // Use a local reference as this method can be called on a worker thread, which can lead
-        // to NullPointer exceptions if mShellTransitions is modified on the main thread.
-        IShellTransitions shellTransitions = mShellTransitions;
-        if (shellTransitions != null) {
-            try {
-                return mShellTransitions.getHomeTaskOverlayContainer();
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call getOverlayContainerForTask", e);
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Use SystemUI's transaction-queue instead of Launcher's independent one. This is necessary
-     * if Launcher and SystemUI need to coordinate transactions (eg. for shell transitions).
-     */
-    public void shareTransactionQueue() {
-        if (mOriginalTransactionToken == null) {
-            mOriginalTransactionToken = SurfaceControl.Transaction.getDefaultApplyToken();
-        }
-        setupTransactionQueue();
-    }
-
-    /**
-     * Switch back to using Launcher's independent transaction queue.
-     */
-    public void unshareTransactionQueue() {
-        if (mOriginalTransactionToken == null) {
-            return;
-        }
-        SurfaceControl.Transaction.setDefaultApplyToken(mOriginalTransactionToken);
-        mOriginalTransactionToken = null;
-    }
-
-    private void setupTransactionQueue() {
-        if (mOriginalTransactionToken == null) {
-            return;
-        }
-        if (mShellTransitions == null) {
-            SurfaceControl.Transaction.setDefaultApplyToken(mOriginalTransactionToken);
-            return;
-        }
-        final IBinder shellApplyToken;
-        try {
-            shellApplyToken = mShellTransitions.getShellApplyToken();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error getting Shell's apply token", e);
-            return;
-        }
-        if (shellApplyToken == null) {
-            Log.e(TAG, "Didn't receive apply token from Shell");
-            return;
-        }
-        SurfaceControl.Transaction.setDefaultApplyToken(shellApplyToken);
-    }
-
-    //
-    // Starting window
-    //
-
-    /**
-     * Sets listener to get callbacks when launching a task.
-     */
-    public void setStartingWindowListener(IStartingWindowListener listener) {
-        if (mStartingWindow != null) {
-            try {
-                mStartingWindow.setStartingWindowListener(listener);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call setStartingWindowListener", e);
-            }
-        }
-        mStartingWindowListener = listener;
-    }
-
-    //
-    // SmartSpace transitions
-    //
-
-    /**
-     * Sets the instance of {@link ILauncherUnlockAnimationController} that System UI should use to
-     * control the launcher side of the unlock animation. This will also cause us to dispatch the
-     * current state of the smartspace to System UI (this will subsequently happen if the state
-     * changes).
-     */
-    public void setLauncherUnlockAnimationController(
-            String activityClass, ILauncherUnlockAnimationController controller) {
-        if (mSysuiUnlockAnimationController != null) {
-            try {
-                mSysuiUnlockAnimationController.setLauncherUnlockController(
-                        activityClass, controller);
-                if (controller != null) {
-                    controller.dispatchSmartspaceStateToSysui();
-                }
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call setLauncherUnlockAnimationController", e);
-            }
-        }
-        mLauncherActivityClass = activityClass;
-        mLauncherUnlockAnimationController = controller;
-    }
-
-    /**
-     * Tells System UI that the Launcher's smartspace state has been updated, so that it can prepare
-     * the unlock animation accordingly.
-     */
-    public void notifySysuiSmartspaceStateUpdated(SmartspaceState state) {
-        if (mSysuiUnlockAnimationController != null) {
-            try {
-                mSysuiUnlockAnimationController.onLauncherSmartspaceStateUpdated(state);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call notifySysuiSmartspaceStateUpdated", e);
-                e.printStackTrace();
-            }
-        }
-    }
-
-    //
-    // Recents
-    //
-
-    public void registerRecentTasksListener(IRecentTasksListener listener) {
-        if (mRecentTasks != null) {
-            try {
-                mRecentTasks.registerRecentTasksListener(listener);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call registerRecentTasksListener", e);
-            }
-        }
-        mRecentTasksListener = listener;
-    }
-
-    public void unregisterRecentTasksListener(IRecentTasksListener listener) {
-        if (mRecentTasks != null) {
-            try {
-                mRecentTasks.unregisterRecentTasksListener(listener);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call unregisterRecentTasksListener");
-            }
-        }
-        mRecentTasksListener = null;
-    }
-
-    //
-    // Back navigation transitions
-    //
-
-    /** Sets the launcher {@link android.window.IOnBackInvokedCallback} to shell */
-    public void setBackToLauncherCallback(IOnBackInvokedCallback callback,
-            IRemoteAnimationRunner runner) {
-        mBackToLauncherCallback = callback;
-        mBackToLauncherRunner = runner;
-        if (mBackAnimation == null || mBackToLauncherCallback == null) {
-            return;
-        }
-        try {
-            mBackAnimation.setBackToLauncherCallback(callback, runner);
-        } catch (RemoteException | SecurityException e) {
-            Log.e(TAG, "Failed call setBackToLauncherCallback", e);
-        }
-    }
-
-    /** Clears the previously registered {@link IOnBackInvokedCallback}.
-     *
-     * @param callback The previously registered callback instance.
-     */
-    public void clearBackToLauncherCallback(IOnBackInvokedCallback callback) {
-        if (mBackToLauncherCallback != callback) {
-            return;
-        }
-        mBackToLauncherCallback = null;
-        mBackToLauncherRunner = null;
-        if (mBackAnimation == null) {
-            return;
-        }
-        try {
-            mBackAnimation.clearBackToLauncherCallback();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed call clearBackToLauncherCallback", e);
-        }
-    }
-
-    /**
-     * Called when the status bar color needs to be customized when back navigation.
-     */
-    public void customizeStatusBarAppearance(AppearanceRegion appearance) {
-        if (mBackAnimation == null) {
-            return;
-        }
-        try {
-            mBackAnimation.customizeStatusBarAppearance(appearance);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed call useLauncherSysBarFlags", e);
-        }
-    }
-
-    public static class GetRecentTasksException extends Exception {
-        public GetRecentTasksException(String message) {
-            super(message);
-        }
-
-        public GetRecentTasksException(String message, Throwable cause) {
-            super(message, cause);
-        }
-    }
-
-    /**
-     * Retrieves a list of Recent tasks from ActivityManager.
-     * @throws GetRecentTasksException if IRecentTasks is not initialized, or when we get
-     * RemoteException from server side
-     */
-    public ArrayList<GroupedTaskInfo> getRecentTasks(int numTasks,
-            int userId) throws GetRecentTasksException {
-        if (mRecentTasks == null) {
-            Log.e(TAG, "getRecentTasks() failed due to null mRecentTasks");
-            throw new GetRecentTasksException("null mRecentTasks");
-        }
-        try {
-            final GroupedTaskInfo[] rawTasks =
-                    mRecentTasks.getRecentTasks(numTasks, RECENT_IGNORE_UNAVAILABLE, userId);
-            if (rawTasks == null) {
-                return new ArrayList<>();
-            }
-            return new ArrayList<>(Arrays.asList(rawTasks));
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed call getRecentTasks", e);
-            throw new GetRecentTasksException("Failed call getRecentTasks", e);
-        }
-    }
-
-    /**
-     * Gets the set of running tasks.
-     */
-    public ArrayList<ActivityManager.RunningTaskInfo> getRunningTasks(int numTasks) {
-        if (mRecentTasks != null && shouldEnableRunningTasksForDesktopMode()) {
-            try {
-                return new ArrayList<>(Arrays.asList(mRecentTasks.getRunningTasks(numTasks)));
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call getRunningTasks", e);
-            }
-        }
-        return new ArrayList<>();
-    }
-
-    private boolean shouldEnableRunningTasksForDesktopMode() {
-        return DesktopModeStatus.canEnterDesktopMode(mContext)
-                && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS.isTrue();
-    }
-
-    private boolean handleMessageAsync(Message msg) {
-        switch (msg.what) {
-            case MSG_SET_SHELF_HEIGHT:
-                setShelfHeightAsync(msg.arg1, msg.arg2);
-                return true;
-            case MSG_SET_LAUNCHER_KEEP_CLEAR_AREA_HEIGHT:
-                setLauncherKeepClearAreaHeight(msg.arg1, msg.arg2);
-                return true;
-        }
-
-        return false;
-    }
-
-    //
-    // Desktop Mode
-    //
-
-    /** Call shell to show all apps active on the desktop */
-    public void showDesktopApps(int displayId, @Nullable RemoteTransition transition) {
-        if (mDesktopMode != null) {
-            try {
-                mDesktopMode.showDesktopApps(displayId, transition);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call showDesktopApps", e);
-            }
-        }
-    }
-
-    /**
-     * If task with the given id is on the desktop, bring it to front
-     */
-    public void showDesktopApp(int taskId, @Nullable RemoteTransition transition) {
-        if (mDesktopMode != null) {
-            try {
-                mDesktopMode.showDesktopApp(taskId, transition);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call showDesktopApp", e);
-            }
-        }
-    }
-
-    /** Call shell to get number of visible freeform tasks */
-    public int getVisibleDesktopTaskCount(int displayId) {
-        if (mDesktopMode != null) {
-            try {
-                return mDesktopMode.getVisibleTaskCount(displayId);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call getVisibleDesktopTaskCount", e);
-            }
-        }
-        return 0;
-    }
-
-    /** Set a listener on shell to get updates about desktop task state */
-    public void setDesktopTaskListener(@Nullable IDesktopTaskListener listener) {
-        mDesktopTaskListener = listener;
-        if (mDesktopMode != null) {
-            try {
-                mDesktopMode.setTaskListener(listener);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call setDesktopTaskListener", e);
-            }
-        }
-    }
-
-    /** Perform cleanup transactions after animation to split select is complete */
-    public void onDesktopSplitSelectAnimComplete(ActivityManager.RunningTaskInfo taskInfo) {
-        if (mDesktopMode != null) {
-            try {
-                mDesktopMode.onDesktopSplitSelectAnimComplete(taskInfo);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call onDesktopSplitSelectAnimComplete", e);
-            }
-        }
-    }
-
-    /** Call shell to move a task with given `taskId` to desktop  */
-    public void moveToDesktop(int taskId, DesktopModeTransitionSource transitionSource,
-            @Nullable RemoteTransition transition) {
-        if (mDesktopMode != null) {
-            try {
-                mDesktopMode.moveToDesktop(taskId, transitionSource, transition);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call moveToDesktop", e);
-            }
-        }
-    }
-
-    /** Call shell to remove the desktop that is on given `displayId` */
-    public void removeDesktop(int displayId) {
-        if (mDesktopMode != null) {
-            try {
-                mDesktopMode.removeDesktop(displayId);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call removeDesktop", e);
-            }
-        }
-    }
-
-    /** Call shell to move a task with given `taskId` to external display. */
-    public void moveToExternalDisplay(int taskId) {
-        if (mDesktopMode != null) {
-            try {
-                mDesktopMode.moveToExternalDisplay(taskId);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call moveToExternalDisplay", e);
-            }
-        }
-    }
-
-    //
-    // Unfold transition
-    //
-
-    /** Sets the unfold animation lister to sysui. */
-    public void setUnfoldAnimationListener(IUnfoldTransitionListener callback) {
-        mUnfoldAnimationListener = callback;
-        if (mUnfoldAnimation == null) {
-            return;
-        }
-        try {
-            Log.d(TAG, "Registering unfold animation receiver");
-            mUnfoldAnimation.setListener(callback);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed call setUnfoldAnimationListener", e);
-        }
-    }
-
-    @Nullable
-    public ProxyUnfoldTransitionProvider getUnfoldTransitionProvider() {
-        return mUnfoldTransitionProvider;
-    }
-
-    //
-    // Recents
-    //
-
-    /**
-     * Starts the recents activity. The caller should manage the thread on which this is called.
-     */
-    public boolean startRecentsActivity(Intent intent, ActivityOptions options,
-            RecentsAnimationListener listener, boolean useSyntheticRecentsTransition) {
-        if (mRecentTasks == null) {
-            ActiveGestureProtoLogProxy.logRecentTasksMissing();
-            return false;
-        }
-        final IRecentsAnimationRunner runner = new IRecentsAnimationRunner.Stub() {
-            @Override
-            public void onAnimationStart(IRecentsAnimationController controller,
-                    RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
-                    Rect homeContentInsets, Rect minimizedHomeBounds, Bundle extras) {
-                // Aidl bundles need to explicitly set class loader
-                // https://developer.android.com/guide/components/aidl#Bundles
-                if (extras != null) {
-                    extras.setClassLoader(SplitBounds.class.getClassLoader());
-                }
-                listener.onAnimationStart(new RecentsAnimationControllerCompat(controller), apps,
-                        wallpapers, homeContentInsets, minimizedHomeBounds, extras);
-            }
-
-            @Override
-            public void onAnimationCanceled(int[] taskIds, TaskSnapshot[] taskSnapshots) {
-                listener.onAnimationCanceled(
-                        ThumbnailData.wrap(taskIds, taskSnapshots));
-            }
-
-            @Override
-            public void onTasksAppeared(RemoteAnimationTarget[] apps) {
-                listener.onTasksAppeared(apps);
-            }
-        };
-        final Bundle optsBundle = options.toBundle();
-        if (useSyntheticRecentsTransition) {
-            optsBundle.putBoolean("is_synthetic_recents_transition", true);
-        }
-        try {
-            mRecentTasks.startRecentsTransition(mRecentsPendingIntent, intent, optsBundle,
-                    mContext.getIApplicationThread(), runner);
-            return true;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error starting recents via shell", e);
-            return false;
-        }
-    }
-
-    //
-    // Drag and drop
-    //
-
-    /**
-     * For testing purposes.  Returns `true` only if the shell drop target has shown and
-     * drawn and is ready to handle drag events and the subsequent drop.
-     */
-    public boolean isDragAndDropReady() {
-        if (mDragAndDrop == null) {
-            return false;
-        }
-        try {
-            return mDragAndDrop.isReadyToHandleDrag();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error querying drag state", e);
-            return false;
-        }
-    }
-
-    public void dump(PrintWriter pw) {
-        pw.println(TAG + ":");
-
-        pw.println("\tmSystemUiProxy=" + mSystemUiProxy);
-        pw.println("\tmPip=" + mPip);
-        pw.println("\tmPipAnimationListener=" + mPipAnimationListener);
-        pw.println("\tmBubbles=" + mBubbles);
-        pw.println("\tmBubblesListener=" + mBubblesListener);
-        pw.println("\tmSplitScreen=" + mSplitScreen);
-        pw.println("\tmSplitScreenListener=" + mSplitScreenListener);
-        pw.println("\tmSplitSelectListener=" + mSplitSelectListener);
-        pw.println("\tmOneHanded=" + mOneHanded);
-        pw.println("\tmShellTransitions=" + mShellTransitions);
-        pw.println("\tmHomeVisibilityState=" + mHomeVisibilityState);
-        pw.println("\tmFocusState=" + mFocusState);
-        pw.println("\tmStartingWindow=" + mStartingWindow);
-        pw.println("\tmStartingWindowListener=" + mStartingWindowListener);
-        pw.println("\tmSysuiUnlockAnimationController=" + mSysuiUnlockAnimationController);
-        pw.println("\tmLauncherActivityClass=" + mLauncherActivityClass);
-        pw.println("\tmLauncherUnlockAnimationController=" + mLauncherUnlockAnimationController);
-        pw.println("\tmRecentTasks=" + mRecentTasks);
-        pw.println("\tmRecentTasksListener=" + mRecentTasksListener);
-        pw.println("\tmBackAnimation=" + mBackAnimation);
-        pw.println("\tmBackToLauncherCallback=" + mBackToLauncherCallback);
-        pw.println("\tmBackToLauncherRunner=" + mBackToLauncherRunner);
-        pw.println("\tmDesktopMode=" + mDesktopMode);
-        pw.println("\tmDesktopTaskListener=" + mDesktopTaskListener);
-        pw.println("\tmUnfoldAnimation=" + mUnfoldAnimation);
-        pw.println("\tmUnfoldAnimationListener=" + mUnfoldAnimationListener);
-        pw.println("\tmDragAndDrop=" + mDragAndDrop);
-    }
-
-    /**
-     * Adds all interfaces held by this proxy to the bundle
-     */
-    @VisibleForTesting
-    public void addAllInterfaces(Bundle out) {
-        QuickStepContract.addInterface(mSystemUiProxy, out);
-        QuickStepContract.addInterface(mPip, out);
-        QuickStepContract.addInterface(mBubbles, out);
-        QuickStepContract.addInterface(mSysuiUnlockAnimationController, out);
-        QuickStepContract.addInterface(mSplitScreen, out);
-        QuickStepContract.addInterface(mOneHanded, out);
-        QuickStepContract.addInterface(mShellTransitions, out);
-        QuickStepContract.addInterface(mStartingWindow, out);
-        QuickStepContract.addInterface(mRecentTasks, out);
-        QuickStepContract.addInterface(mBackAnimation, out);
-        QuickStepContract.addInterface(mDesktopMode, out);
-        QuickStepContract.addInterface(mUnfoldAnimation, out);
-        QuickStepContract.addInterface(mDragAndDrop, out);
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.kt b/quickstep/src/com/android/quickstep/SystemUiProxy.kt
new file mode 100644
index 0000000..75694af
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.kt
@@ -0,0 +1,1270 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep
+
+import android.app.ActivityManager
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.ActivityOptions
+import android.app.PendingIntent
+import android.app.PictureInPictureParams
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.ShortcutInfo
+import android.graphics.Point
+import android.graphics.Rect
+import android.os.Bundle
+import android.os.Handler
+import android.os.IBinder
+import android.os.Message
+import android.os.RemoteException
+import android.os.UserHandle
+import android.util.Log
+import android.view.IRemoteAnimationRunner
+import android.view.KeyEvent
+import android.view.MotionEvent
+import android.view.RemoteAnimationTarget
+import android.view.SurfaceControl
+import android.view.SurfaceControl.Transaction
+import android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS
+import android.window.IOnBackInvokedCallback
+import android.window.RemoteTransition
+import android.window.TaskSnapshot
+import android.window.TransitionFilter
+import androidx.annotation.MainThread
+import androidx.annotation.VisibleForTesting
+import androidx.annotation.WorkerThread
+import com.android.internal.logging.InstanceId
+import com.android.internal.util.ScreenshotRequest
+import com.android.internal.view.AppearanceRegion
+import com.android.launcher3.Flags
+import com.android.launcher3.dagger.ApplicationContext
+import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppSingleton
+import com.android.launcher3.util.DaggerSingletonObject
+import com.android.launcher3.util.Executors
+import com.android.launcher3.util.Preconditions
+import com.android.launcher3.util.SplitConfigurationOptions.StagePosition
+import com.android.quickstep.util.ActiveGestureProtoLogProxy
+import com.android.quickstep.util.ContextualSearchInvoker
+import com.android.quickstep.util.unfold.ProxyUnfoldTransitionProvider
+import com.android.systemui.shared.recents.ISystemUiProxy
+import com.android.systemui.shared.recents.model.ThumbnailData.Companion.wrap
+import com.android.systemui.shared.system.QuickStepContract
+import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags
+import com.android.systemui.shared.system.RecentsAnimationControllerCompat
+import com.android.systemui.shared.system.RecentsAnimationListener
+import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController
+import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController
+import com.android.systemui.shared.system.smartspace.SmartspaceState
+import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig
+import com.android.systemui.unfold.progress.IUnfoldAnimation
+import com.android.systemui.unfold.progress.IUnfoldTransitionListener
+import com.android.wm.shell.back.IBackAnimation
+import com.android.wm.shell.bubbles.IBubbles
+import com.android.wm.shell.bubbles.IBubblesListener
+import com.android.wm.shell.common.pip.IPip
+import com.android.wm.shell.common.pip.IPipAnimationListener
+import com.android.wm.shell.desktopmode.IDesktopMode
+import com.android.wm.shell.desktopmode.IDesktopTaskListener
+import com.android.wm.shell.draganddrop.IDragAndDrop
+import com.android.wm.shell.onehanded.IOneHanded
+import com.android.wm.shell.recents.IRecentTasks
+import com.android.wm.shell.recents.IRecentTasksListener
+import com.android.wm.shell.recents.IRecentsAnimationController
+import com.android.wm.shell.recents.IRecentsAnimationRunner
+import com.android.wm.shell.shared.GroupedTaskInfo
+import com.android.wm.shell.shared.IShellTransitions
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation.UpdateSource
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
+import com.android.wm.shell.shared.split.SplitBounds
+import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition
+import com.android.wm.shell.splitscreen.ISplitScreen
+import com.android.wm.shell.splitscreen.ISplitScreenListener
+import com.android.wm.shell.splitscreen.ISplitSelectListener
+import com.android.wm.shell.startingsurface.IStartingWindow
+import com.android.wm.shell.startingsurface.IStartingWindowListener
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/** Holds the reference to SystemUI. */
+@LauncherAppSingleton
+class SystemUiProxy @Inject constructor(@ApplicationContext private val context: Context) :
+    NavHandle {
+
+    private var systemUiProxy: ISystemUiProxy? = null
+    private var pip: IPip? = null
+    private var bubbles: IBubbles? = null
+    private var sysuiUnlockAnimationController: ISysuiUnlockAnimationController? = null
+    private var splitScreen: ISplitScreen? = null
+    private var oneHanded: IOneHanded? = null
+    private var shellTransitions: IShellTransitions? = null
+    private var startingWindow: IStartingWindow? = null
+    private var recentTasks: IRecentTasks? = null
+    private var backAnimation: IBackAnimation? = null
+    private var desktopMode: IDesktopMode? = null
+    private var unfoldAnimation: IUnfoldAnimation? = null
+
+    private val systemUiProxyDeathRecipient =
+        IBinder.DeathRecipient { Executors.MAIN_EXECUTOR.execute { clearProxy() } }
+
+    // Save the listeners passed into the proxy since OverviewProxyService may not have been bound
+    // yet, and we'll need to set/register these listeners with SysUI when they do.  Note that it is
+    // up to the caller to clear the listeners to prevent leaks as these can be held indefinitely
+    // in case SysUI needs to rebind.
+    private var pipAnimationListener: IPipAnimationListener? = null
+    private var bubblesListener: IBubblesListener? = null
+    private var splitScreenListener: ISplitScreenListener? = null
+    private var splitSelectListener: ISplitSelectListener? = null
+    private var startingWindowListener: IStartingWindowListener? = null
+    private var launcherUnlockAnimationController: ILauncherUnlockAnimationController? = null
+    private var launcherActivityClass: String? = null
+    private var recentTasksListener: IRecentTasksListener? = null
+    private var unfoldAnimationListener: IUnfoldTransitionListener? = null
+    private var desktopTaskListener: IDesktopTaskListener? = null
+    private val remoteTransitions = LinkedHashMap<RemoteTransition, TransitionFilter>()
+
+    private val stateChangeCallbacks: MutableList<Runnable> = ArrayList()
+
+    private var originalTransactionToken: IBinder? = null
+    private var backToLauncherCallback: IOnBackInvokedCallback? = null
+    private var backToLauncherRunner: IRemoteAnimationRunner? = null
+    private var dragAndDrop: IDragAndDrop? = null
+    val homeVisibilityState = HomeVisibilityState()
+    private val focusState = FocusState()
+
+    // Used to dedupe calls to SystemUI
+    private var lastShelfHeight = 0
+    private var lastShelfVisible = false
+
+    // Used to dedupe calls to SystemUI
+    private var lastLauncherKeepClearAreaHeight = 0
+    private var lastLauncherKeepClearAreaHeightVisible = false
+
+    private val asyncHandler =
+        Handler(Executors.UI_HELPER_EXECUTOR.looper) { handleMessageAsync(it) }
+
+    // TODO(141886704): Find a way to remove this
+    @SystemUiStateFlags var lastSystemUiStateFlags: Long = 0
+
+    /**
+     * This is a singleton pending intent that is used to start recents via Shell (which is a
+     * different process). It is bare-bones, so it's expected that the component and options will be
+     * provided via fill-in intent.
+     */
+    private val recentsPendingIntent =
+        PendingIntent.getActivity(
+            context,
+            0,
+            Intent().setPackage(context.packageName),
+            PendingIntent.FLAG_MUTABLE or
+                PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT or
+                Intent.FILL_IN_COMPONENT,
+            ActivityOptions.makeBasic()
+                .setPendingIntentCreatorBackgroundActivityStartMode(
+                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+                )
+                .toBundle(),
+        )
+
+    val unfoldTransitionProvider: ProxyUnfoldTransitionProvider? =
+        if ((Flags.enableUnfoldStateAnimation() && ResourceUnfoldTransitionConfig().isEnabled))
+            ProxyUnfoldTransitionProvider()
+        else null
+
+    private inline fun executeWithErrorLog(
+        errorMsg: () -> String,
+        tag: String = TAG,
+        callback: () -> Any?,
+    ) {
+        try {
+            callback.invoke()
+        } catch (e: RemoteException) {
+            Log.w(tag, errorMsg.invoke(), e)
+        }
+    }
+
+    fun onBackEvent(backEvent: KeyEvent?) =
+        executeWithErrorLog({ "Failed call onBackPressed" }) {
+            systemUiProxy?.onBackEvent(backEvent)
+        }
+
+    fun onImeSwitcherPressed() =
+        executeWithErrorLog({ "Failed call onImeSwitcherPressed" }) {
+            systemUiProxy?.onImeSwitcherPressed()
+        }
+
+    fun onImeSwitcherLongPress() =
+        executeWithErrorLog({ "Failed call onImeSwitcherLongPress" }) {
+            systemUiProxy?.onImeSwitcherLongPress()
+        }
+
+    fun updateContextualEduStats(isTrackpadGesture: Boolean, gestureType: String) =
+        executeWithErrorLog({ "Failed call updateContextualEduStats" }) {
+            systemUiProxy?.updateContextualEduStats(isTrackpadGesture, gestureType)
+        }
+
+    fun setHomeRotationEnabled(enabled: Boolean) =
+        executeWithErrorLog({ "Failed call setHomeRotationEnabled" }) {
+            systemUiProxy?.setHomeRotationEnabled(enabled)
+        }
+
+    /**
+     * Sets proxy state, including death linkage, various listeners, and other configuration objects
+     */
+    @MainThread
+    fun setProxy(
+        proxy: ISystemUiProxy?,
+        pip: IPip?,
+        bubbles: IBubbles?,
+        splitScreen: ISplitScreen?,
+        oneHanded: IOneHanded?,
+        shellTransitions: IShellTransitions?,
+        startingWindow: IStartingWindow?,
+        recentTasks: IRecentTasks?,
+        sysuiUnlockAnimationController: ISysuiUnlockAnimationController?,
+        backAnimation: IBackAnimation?,
+        desktopMode: IDesktopMode?,
+        unfoldAnimation: IUnfoldAnimation?,
+        dragAndDrop: IDragAndDrop?,
+    ) {
+        Preconditions.assertUIThread()
+        unlinkToDeath()
+        systemUiProxy = proxy
+        this.pip = pip
+        this.bubbles = bubbles
+        this.splitScreen = splitScreen
+        this.oneHanded = oneHanded
+        this.shellTransitions = shellTransitions
+        this.startingWindow = startingWindow
+        this.sysuiUnlockAnimationController = sysuiUnlockAnimationController
+        this.recentTasks = recentTasks
+        this.backAnimation = backAnimation
+        this.desktopMode = desktopMode
+        this.unfoldAnimation = if (Flags.enableUnfoldStateAnimation()) null else unfoldAnimation
+        this.dragAndDrop = dragAndDrop
+        linkToDeath()
+        // re-attach the listeners once missing due to setProxy has not been initialized yet.
+        setPipAnimationListener(pipAnimationListener)
+        setBubblesListener(bubblesListener)
+        registerSplitScreenListener(splitScreenListener)
+        registerSplitSelectListener(splitSelectListener)
+        homeVisibilityState.init(this.shellTransitions)
+        focusState.init(this.shellTransitions)
+        setStartingWindowListener(startingWindowListener)
+        setLauncherUnlockAnimationController(
+            launcherActivityClass,
+            launcherUnlockAnimationController,
+        )
+        LinkedHashMap(remoteTransitions).forEach { (remoteTransition, filter) ->
+            registerRemoteTransition(remoteTransition, filter)
+        }
+        setupTransactionQueue()
+        registerRecentTasksListener(recentTasksListener)
+        setBackToLauncherCallback(backToLauncherCallback, backToLauncherRunner)
+        setUnfoldAnimationListener(unfoldAnimationListener)
+        setDesktopTaskListener(desktopTaskListener)
+        setAssistantOverridesRequested(
+            ContextualSearchInvoker(context).getSysUiAssistOverrideInvocationTypes()
+        )
+        stateChangeCallbacks.forEach { it.run() }
+
+        if (unfoldTransitionProvider != null) {
+            if (unfoldAnimation != null) {
+                try {
+                    unfoldAnimation.setListener(unfoldTransitionProvider)
+                    unfoldTransitionProvider.isActive = true
+                } catch (e: RemoteException) {
+                    // Ignore
+                }
+            } else {
+                unfoldTransitionProvider.isActive = false
+            }
+        }
+    }
+
+    /**
+     * Clear the proxy to release held resources and turn the majority of its operations into no-ops
+     */
+    @MainThread
+    fun clearProxy() =
+        setProxy(null, null, null, null, null, null, null, null, null, null, null, null, null)
+
+    /** Adds a callback to be notified whenever the active state changes */
+    fun addOnStateChangeListener(callback: Runnable) = stateChangeCallbacks.add(callback)
+
+    /** Removes a previously added state change callback */
+    fun removeOnStateChangeListener(callback: Runnable) = stateChangeCallbacks.remove(callback)
+
+    fun isActive() = systemUiProxy != null
+
+    private fun linkToDeath() =
+        executeWithErrorLog({ "Failed to link sysui proxy death recipient" }) {
+            systemUiProxy?.asBinder()?.linkToDeath(systemUiProxyDeathRecipient, 0 /* flags */)
+        }
+
+    private fun unlinkToDeath() =
+        systemUiProxy?.asBinder()?.unlinkToDeath(systemUiProxyDeathRecipient, 0 /* flags */)
+
+    fun startScreenPinning(taskId: Int) =
+        executeWithErrorLog({ "Failed call startScreenPinning" }) {
+            systemUiProxy?.startScreenPinning(taskId)
+        }
+
+    fun onOverviewShown(fromHome: Boolean, tag: String = TAG) =
+        executeWithErrorLog(
+            { "Failed call onOverviewShown from: ${(if (fromHome) "home" else "app")}" },
+            tag = tag,
+        ) {
+            systemUiProxy?.onOverviewShown(fromHome)
+        }
+
+    @MainThread
+    fun onStatusBarTouchEvent(event: MotionEvent) {
+        Preconditions.assertUIThread()
+        executeWithErrorLog({ "Failed call onStatusBarTouchEvent with arg: $event" }) {
+            systemUiProxy?.onStatusBarTouchEvent(event)
+        }
+    }
+
+    fun onStatusBarTrackpadEvent(event: MotionEvent) =
+        executeWithErrorLog({ "Failed call onStatusBarTrackpadEvent with arg: $event" }) {
+            systemUiProxy?.onStatusBarTrackpadEvent(event)
+        }
+
+    fun onAssistantProgress(progress: Float) =
+        executeWithErrorLog({ "Failed call onAssistantProgress with progress: $progress" }) {
+            systemUiProxy?.onAssistantProgress(progress)
+        }
+
+    fun onAssistantGestureCompletion(velocity: Float) =
+        executeWithErrorLog({ "Failed call onAssistantGestureCompletion" }) {
+            systemUiProxy?.onAssistantGestureCompletion(velocity)
+        }
+
+    fun startAssistant(args: Bundle) =
+        executeWithErrorLog({ "Failed call startAssistant" }) {
+            systemUiProxy?.startAssistant(args)
+        }
+
+    fun setAssistantOverridesRequested(invocationTypes: IntArray) =
+        executeWithErrorLog({ "Failed call setAssistantOverridesRequested" }) {
+            systemUiProxy?.setAssistantOverridesRequested(invocationTypes)
+        }
+
+    override fun animateNavBarLongPress(isTouchDown: Boolean, shrink: Boolean, durationMs: Long) =
+        executeWithErrorLog({ "Failed call animateNavBarLongPress" }) {
+            systemUiProxy?.animateNavBarLongPress(isTouchDown, shrink, durationMs)
+        }
+
+    fun setOverrideHomeButtonLongPress(duration: Long, slopMultiplier: Float, haptic: Boolean) =
+        executeWithErrorLog({ "Failed call setOverrideHomeButtonLongPress" }) {
+            systemUiProxy?.setOverrideHomeButtonLongPress(duration, slopMultiplier, haptic)
+        }
+
+    fun notifyAccessibilityButtonClicked(displayId: Int) =
+        executeWithErrorLog({ "Failed call notifyAccessibilityButtonClicked" }) {
+            systemUiProxy?.notifyAccessibilityButtonClicked(displayId)
+        }
+
+    fun notifyAccessibilityButtonLongClicked() =
+        executeWithErrorLog({ "Failed call notifyAccessibilityButtonLongClicked" }) {
+            systemUiProxy?.notifyAccessibilityButtonLongClicked()
+        }
+
+    fun stopScreenPinning() =
+        executeWithErrorLog({ "Failed call stopScreenPinning" }) {
+            systemUiProxy?.stopScreenPinning()
+        }
+
+    fun notifyPrioritizedRotation(rotation: Int) =
+        executeWithErrorLog({ "Failed call notifyPrioritizedRotation with arg: $rotation" }) {
+            systemUiProxy?.notifyPrioritizedRotation(rotation)
+        }
+
+    fun notifyTaskbarStatus(visible: Boolean, stashed: Boolean) =
+        executeWithErrorLog({ "Failed call notifyTaskbarStatus with arg: $visible, $stashed" }) {
+            systemUiProxy?.notifyTaskbarStatus(visible, stashed)
+        }
+
+    /**
+     * NOTE: If called to suspend, caller MUST call this method to also un-suspend. [suspend] should
+     * be `true` to stop auto-hide, `false` to resume normal behavior
+     */
+    fun notifyTaskbarAutohideSuspend(suspend: Boolean) =
+        executeWithErrorLog({ "Failed call notifyTaskbarAutohideSuspend with arg: $suspend" }) {
+            systemUiProxy?.notifyTaskbarAutohideSuspend(suspend)
+        }
+
+    fun takeScreenshot(request: ScreenshotRequest) =
+        executeWithErrorLog({ "Failed call takeScreenshot" }) {
+            systemUiProxy?.takeScreenshot(request)
+        }
+
+    fun expandNotificationPanel() =
+        executeWithErrorLog({ "Failed call expandNotificationPanel" }) {
+            systemUiProxy?.expandNotificationPanel()
+        }
+
+    fun toggleNotificationPanel() =
+        executeWithErrorLog({ "Failed call toggleNotificationPanel" }) {
+            systemUiProxy?.toggleNotificationPanel()
+        }
+
+    fun toggleQuickSettingsPanel() =
+        executeWithErrorLog({ "Failed call toggleQuickSettingsPanel" }) {
+            systemUiProxy?.toggleQuickSettingsPanel()
+        }
+
+    //
+    // Pip
+    //
+    /** Sets the shelf height. */
+    fun setShelfHeight(visible: Boolean, shelfHeight: Int) =
+        Message.obtain(asyncHandler, MSG_SET_SHELF_HEIGHT, if (visible) 1 else 0, shelfHeight)
+            .sendToTarget()
+
+    @WorkerThread
+    private fun setShelfHeightAsync(visibleInt: Int, shelfHeight: Int) {
+        val visible = visibleInt != 0
+        val changed = visible != lastShelfVisible || shelfHeight != lastShelfHeight
+        val pip = pip
+        if (pip != null && changed) {
+            lastShelfVisible = visible
+            lastShelfHeight = shelfHeight
+            executeWithErrorLog({
+                "Failed call setShelfHeight visible: $visible height: $shelfHeight"
+            }) {
+                pip.setShelfHeight(visible, shelfHeight)
+            }
+        }
+    }
+
+    /**
+     * Sets the height of the keep clear area that is going to be reported by the Launcher for the
+     * Hotseat.
+     */
+    fun setLauncherKeepClearAreaHeight(visible: Boolean, height: Int) =
+        Message.obtain(
+                asyncHandler,
+                MSG_SET_LAUNCHER_KEEP_CLEAR_AREA_HEIGHT,
+                if (visible) 1 else 0,
+                height,
+            )
+            .sendToTarget()
+
+    @WorkerThread
+    private fun setLauncherKeepClearAreaHeight(visibleInt: Int, height: Int) {
+        val visible = visibleInt != 0
+        val changed =
+            visible != lastLauncherKeepClearAreaHeightVisible ||
+                height != lastLauncherKeepClearAreaHeight
+        val pip = pip
+        if (pip != null && changed) {
+            lastLauncherKeepClearAreaHeightVisible = visible
+            lastLauncherKeepClearAreaHeight = height
+            executeWithErrorLog({
+                "Failed call setLauncherKeepClearAreaHeight visible: $visible height: $height"
+            }) {
+                pip.setLauncherKeepClearAreaHeight(visible, height)
+            }
+        }
+    }
+
+    /** Sets listener to get pip animation callbacks. */
+    fun setPipAnimationListener(listener: IPipAnimationListener?) {
+        executeWithErrorLog({ "Failed call setPinnedStackAnimationListener" }) {
+            pip?.setPipAnimationListener(listener)
+        }
+        pipAnimationListener = listener
+    }
+
+    /** @return Destination bounds of auto-pip animation, `null` if the animation is not ready. */
+    fun startSwipePipToHome(
+        componentName: ComponentName?,
+        activityInfo: ActivityInfo?,
+        pictureInPictureParams: PictureInPictureParams?,
+        launcherRotation: Int,
+        hotseatKeepClearArea: Rect?,
+    ): Rect? {
+        executeWithErrorLog({ "Failed call startSwipePipToHome" }) {
+            return pip?.startSwipePipToHome(
+                componentName,
+                activityInfo,
+                pictureInPictureParams,
+                launcherRotation,
+                hotseatKeepClearArea,
+            )
+        }
+        return null
+    }
+
+    /**
+     * Notifies WM Shell that launcher has finished the preparation of the animation for swipe to
+     * home. WM Shell can choose to fade out the overlay when entering PIP is finished, and WM Shell
+     * should be responsible for cleaning up the overlay.
+     */
+    fun stopSwipePipToHome(
+        taskId: Int,
+        componentName: ComponentName?,
+        destinationBounds: Rect?,
+        overlay: SurfaceControl?,
+        appBounds: Rect?,
+        sourceRectHint: Rect?,
+    ) =
+        executeWithErrorLog({ "Failed call stopSwipePipToHome" }) {
+            pip?.stopSwipePipToHome(
+                taskId,
+                componentName,
+                destinationBounds,
+                overlay,
+                appBounds,
+                sourceRectHint,
+            )
+        }
+
+    /**
+     * Notifies WM Shell that launcher has aborted all the animation for swipe to home. WM Shell can
+     * use this callback to clean up its internal states.
+     */
+    fun abortSwipePipToHome(taskId: Int, componentName: ComponentName?) =
+        executeWithErrorLog({ "Failed call abortSwipePipToHome" }) {
+            pip?.abortSwipePipToHome(taskId, componentName)
+        }
+
+    /** Sets the next pip animation type to be the alpha animation. */
+    fun setPipAnimationTypeToAlpha() =
+        executeWithErrorLog({ "Failed call setPipAnimationTypeToAlpha" }) {
+            pip?.setPipAnimationTypeToAlpha()
+        }
+
+    /** Sets the app icon size in pixel used by Launcher all apps. */
+    fun setLauncherAppIconSize(iconSizePx: Int) =
+        executeWithErrorLog({ "Failed call setLauncherAppIconSize" }) {
+            pip?.setLauncherAppIconSize(iconSizePx)
+        }
+
+    //
+    // Bubbles
+    //
+    /** Sets the listener to be notified of bubble state changes. */
+    fun setBubblesListener(listener: IBubblesListener?) {
+        executeWithErrorLog({ "Failed call registerBubblesListener" }) {
+            bubbles?.apply {
+                bubblesListener?.let { unregisterBubbleListener(it) }
+                listener?.let { registerBubbleListener(it) }
+            }
+        }
+        bubblesListener = listener
+    }
+
+    /**
+     * Tells SysUI to show the bubble with the provided key.
+     *
+     * @param key the key of the bubble to show.
+     * @param top top coordinate of bubble bar on screen
+     */
+    fun showBubble(key: String?, top: Int) =
+        executeWithErrorLog({ "Failed call showBubble" }) { bubbles?.showBubble(key, top) }
+
+    /** Tells SysUI to remove all bubbles. */
+    fun removeAllBubbles() =
+        executeWithErrorLog({ "Failed call removeAllBubbles" }) { bubbles?.removeAllBubbles() }
+
+    /** Tells SysUI to collapse the bubbles. */
+    fun collapseBubbles() =
+        executeWithErrorLog({ "Failed call collapseBubbles" }) { bubbles?.collapseBubbles() }
+
+    /**
+     * Tells SysUI when the bubble is being dragged. Should be called only when the bubble bar is
+     * expanded.
+     *
+     * @param bubbleKey key of the bubble being dragged
+     */
+    fun startBubbleDrag(bubbleKey: String?) =
+        executeWithErrorLog({ "Failed call startBubbleDrag" }) {
+            bubbles?.startBubbleDrag(bubbleKey)
+        }
+
+    /**
+     * Tells SysUI when the bubble stops being dragged. Should be called only when the bubble bar is
+     * expanded.
+     *
+     * @param location location of the bubble bar
+     * @param top new top coordinate for bubble bar on screen
+     */
+    fun stopBubbleDrag(location: BubbleBarLocation?, top: Int) =
+        executeWithErrorLog({ "Failed call stopBubbleDrag" }) {
+            bubbles?.stopBubbleDrag(location, top)
+        }
+
+    /**
+     * Tells SysUI to dismiss the bubble with the provided key.
+     *
+     * @param key the key of the bubble to dismiss.
+     * @param timestamp the timestamp when the removal happened.
+     */
+    fun dragBubbleToDismiss(key: String?, timestamp: Long) =
+        executeWithErrorLog({ "Failed call dragBubbleToDismiss" }) {
+            bubbles?.dragBubbleToDismiss(key, timestamp)
+        }
+
+    /**
+     * Tells SysUI to show user education relative to the reference point provided.
+     *
+     * @param position the bubble bar top center position in Screen coordinates.
+     */
+    fun showUserEducation(position: Point) =
+        executeWithErrorLog({ "Failed call showUserEducation" }) {
+            bubbles?.showUserEducation(position.x, position.y)
+        }
+
+    /**
+     * Tells SysUI to update the bubble bar location to the new location.
+     *
+     * @param location new location for the bubble bar
+     * @param source what triggered the location update
+     */
+    fun setBubbleBarLocation(location: BubbleBarLocation?, @UpdateSource source: Int) =
+        executeWithErrorLog({ "Failed call setBubbleBarLocation" }) {
+            bubbles?.setBubbleBarLocation(location, source)
+        }
+
+    /**
+     * Tells SysUI the top coordinate of bubble bar on screen
+     *
+     * @param topOnScreen top coordinate for bubble bar on screen
+     */
+    fun updateBubbleBarTopOnScreen(topOnScreen: Int) =
+        executeWithErrorLog({ "Failed call updateBubbleBarTopOnScreen" }) {
+            bubbles?.updateBubbleBarTopOnScreen(topOnScreen)
+        }
+
+    /**
+     * Tells SysUI to show a shortcut bubble.
+     *
+     * @param info the shortcut info used to create or identify the bubble.
+     */
+    fun showShortcutBubble(info: ShortcutInfo?) =
+        executeWithErrorLog({ "Failed call showShortcutBubble" }) {
+            bubbles?.showShortcutBubble(info)
+        }
+
+    /**
+     * Tells SysUI to show a bubble of an app.
+     *
+     * @param intent the intent used to create the bubble.
+     */
+    fun showAppBubble(intent: Intent?) =
+        executeWithErrorLog({ "Failed call showAppBubble" }) { bubbles?.showAppBubble(intent) }
+
+    /** Tells SysUI to show the expanded view. */
+    fun showExpandedView() =
+        executeWithErrorLog({ "Failed call showExpandedView" }) { bubbles?.showExpandedView() }
+
+    //
+    // Splitscreen
+    //
+    fun registerSplitScreenListener(listener: ISplitScreenListener?) {
+        executeWithErrorLog({ "Failed call registerSplitScreenListener" }) {
+            splitScreen?.registerSplitScreenListener(listener)
+        }
+        splitScreenListener = listener
+    }
+
+    fun unregisterSplitScreenListener(listener: ISplitScreenListener?) {
+        executeWithErrorLog({ "Failed call unregisterSplitScreenListener" }) {
+            splitScreen?.unregisterSplitScreenListener(listener)
+        }
+        splitScreenListener = null
+    }
+
+    fun registerSplitSelectListener(listener: ISplitSelectListener?) {
+        executeWithErrorLog({ "Failed call registerSplitSelectListener" }) {
+            splitScreen?.registerSplitSelectListener(listener)
+        }
+        splitSelectListener = listener
+    }
+
+    fun unregisterSplitSelectListener(listener: ISplitSelectListener?) {
+        executeWithErrorLog({ "Failed call unregisterSplitSelectListener" }) {
+            splitScreen?.unregisterSplitSelectListener(listener)
+        }
+        splitSelectListener = null
+    }
+
+    /** Start multiple tasks in split-screen simultaneously. */
+    fun startTasks(
+        taskId1: Int,
+        options1: Bundle?,
+        taskId2: Int,
+        options2: Bundle?,
+        @StagePosition splitPosition: Int,
+        @PersistentSnapPosition snapPosition: Int,
+        remoteTransition: RemoteTransition?,
+        instanceId: InstanceId?,
+    ) =
+        executeWithErrorLog({ "Failed call startTasks" }) {
+            splitScreen?.startTasks(
+                taskId1,
+                options1,
+                taskId2,
+                options2,
+                splitPosition,
+                snapPosition,
+                remoteTransition,
+                instanceId,
+            )
+        }
+
+    fun startIntentAndTask(
+        pendingIntent: PendingIntent?,
+        userId1: Int,
+        options1: Bundle?,
+        taskId: Int,
+        options2: Bundle?,
+        @StagePosition splitPosition: Int,
+        @PersistentSnapPosition snapPosition: Int,
+        remoteTransition: RemoteTransition?,
+        instanceId: InstanceId?,
+    ) =
+        executeWithErrorLog({ "Failed call startIntentAndTask" }) {
+            splitScreen?.startIntentAndTask(
+                pendingIntent,
+                userId1,
+                options1,
+                taskId,
+                options2,
+                splitPosition,
+                snapPosition,
+                remoteTransition,
+                instanceId,
+            )
+        }
+
+    fun startIntents(
+        pendingIntent1: PendingIntent?,
+        userId1: Int,
+        shortcutInfo1: ShortcutInfo?,
+        options1: Bundle?,
+        pendingIntent2: PendingIntent?,
+        userId2: Int,
+        shortcutInfo2: ShortcutInfo?,
+        options2: Bundle?,
+        @StagePosition splitPosition: Int,
+        @PersistentSnapPosition snapPosition: Int,
+        remoteTransition: RemoteTransition?,
+        instanceId: InstanceId?,
+    ) =
+        executeWithErrorLog({ "Failed call startIntents" }) {
+            splitScreen?.startIntents(
+                pendingIntent1,
+                userId1,
+                shortcutInfo1,
+                options1,
+                pendingIntent2,
+                userId2,
+                shortcutInfo2,
+                options2,
+                splitPosition,
+                snapPosition,
+                remoteTransition,
+                instanceId,
+            )
+        }
+
+    fun startShortcutAndTask(
+        shortcutInfo: ShortcutInfo?,
+        options1: Bundle?,
+        taskId: Int,
+        options2: Bundle?,
+        @StagePosition splitPosition: Int,
+        @PersistentSnapPosition snapPosition: Int,
+        remoteTransition: RemoteTransition?,
+        instanceId: InstanceId?,
+    ) =
+        executeWithErrorLog({ "Failed call startShortcutAndTask" }) {
+            splitScreen?.startShortcutAndTask(
+                shortcutInfo,
+                options1,
+                taskId,
+                options2,
+                splitPosition,
+                snapPosition,
+                remoteTransition,
+                instanceId,
+            )
+        }
+
+    fun startShortcut(
+        packageName: String?,
+        shortcutId: String?,
+        position: Int,
+        options: Bundle?,
+        user: UserHandle?,
+        instanceId: InstanceId?,
+    ) =
+        executeWithErrorLog({ "Failed call startShortcut" }) {
+            splitScreen?.startShortcut(packageName, shortcutId, position, options, user, instanceId)
+        }
+
+    fun startIntent(
+        intent: PendingIntent?,
+        userId: Int,
+        fillInIntent: Intent?,
+        position: Int,
+        options: Bundle?,
+        instanceId: InstanceId?,
+    ) =
+        executeWithErrorLog({ "Failed call startIntent" }) {
+            splitScreen?.startIntent(intent, userId, fillInIntent, position, options, instanceId)
+        }
+
+    //
+    // One handed
+    //
+    fun startOneHandedMode() =
+        executeWithErrorLog({ "Failed call startOneHandedMode" }) { oneHanded?.startOneHanded() }
+
+    fun stopOneHandedMode() =
+        executeWithErrorLog({ "Failed call stopOneHandedMode" }) { oneHanded?.stopOneHanded() }
+
+    //
+    // Remote transitions
+    //
+    fun registerRemoteTransition(remoteTransition: RemoteTransition?, filter: TransitionFilter) {
+        remoteTransition ?: return
+        executeWithErrorLog({ "Failed call registerRemoteTransition" }) {
+            shellTransitions?.registerRemote(filter, remoteTransition)
+        }
+        remoteTransitions.putIfAbsent(remoteTransition, filter)
+    }
+
+    fun unregisterRemoteTransition(remoteTransition: RemoteTransition?) {
+        executeWithErrorLog({ "Failed call unregisterRemoteTransition" }) {
+            shellTransitions?.unregisterRemote(remoteTransition)
+        }
+        remoteTransitions.remove(remoteTransition)
+    }
+
+    /**
+     * Returns a surface which can be used to attach overlays to home task or null if the task
+     * doesn't exist or sysui is not connected
+     */
+    fun getHomeTaskOverlayContainer(): SurfaceControl? {
+        executeWithErrorLog({ "Failed call getHomeTaskOverlayContainer" }) {
+            return shellTransitions?.homeTaskOverlayContainer
+        }
+        return null
+    }
+
+    /**
+     * Use SystemUI's transaction-queue instead of Launcher's independent one. This is necessary if
+     * Launcher and SystemUI need to coordinate transactions (eg. for shell transitions).
+     */
+    fun shareTransactionQueue() {
+        if (originalTransactionToken == null) {
+            originalTransactionToken = Transaction.getDefaultApplyToken()
+        }
+        setupTransactionQueue()
+    }
+
+    /** Switch back to using Launcher's independent transaction queue. */
+    fun unshareTransactionQueue() {
+        if (originalTransactionToken == null) {
+            return
+        }
+        Transaction.setDefaultApplyToken(originalTransactionToken)
+        originalTransactionToken = null
+    }
+
+    private fun setupTransactionQueue() =
+        executeWithErrorLog({ "Error getting Shell's apply token" }) {
+            val token: IBinder =
+                shellTransitions?.shellApplyToken ?: originalTransactionToken ?: return
+            Transaction.setDefaultApplyToken(token)
+        }
+
+    //
+    // Starting window
+    //
+    /** Sets listener to get callbacks when launching a task. */
+    fun setStartingWindowListener(listener: IStartingWindowListener?) {
+        executeWithErrorLog({ "Failed call setStartingWindowListener" }) {
+            startingWindow?.setStartingWindowListener(listener)
+        }
+        startingWindowListener = listener
+    }
+
+    //
+    // SmartSpace transitions
+    //
+    /**
+     * Sets the instance of [ILauncherUnlockAnimationController] that System UI should use to
+     * control the launcher side of the unlock animation. This will also cause us to dispatch the
+     * current state of the smartspace to System UI (this will subsequently happen if the state
+     * changes).
+     */
+    fun setLauncherUnlockAnimationController(
+        activityClass: String?,
+        controller: ILauncherUnlockAnimationController?,
+    ) {
+        executeWithErrorLog({ "Failed call setLauncherUnlockAnimationController" }) {
+            sysuiUnlockAnimationController?.apply {
+                setLauncherUnlockController(activityClass, controller)
+                controller?.dispatchSmartspaceStateToSysui()
+            }
+        }
+        launcherActivityClass = activityClass
+        launcherUnlockAnimationController = controller
+    }
+
+    /**
+     * Tells System UI that the Launcher's smartspace state has been updated, so that it can prepare
+     * the unlock animation accordingly.
+     */
+    fun notifySysuiSmartspaceStateUpdated(state: SmartspaceState?) =
+        executeWithErrorLog({ "Failed call notifySysuiSmartspaceStateUpdated" }) {
+            sysuiUnlockAnimationController?.onLauncherSmartspaceStateUpdated(state)
+        }
+
+    //
+    // Recents
+    //
+    fun registerRecentTasksListener(listener: IRecentTasksListener?) {
+        executeWithErrorLog({ "Failed call registerRecentTasksListener" }) {
+            recentTasks?.registerRecentTasksListener(listener)
+        }
+        recentTasksListener = listener
+    }
+
+    fun unregisterRecentTasksListener(listener: IRecentTasksListener?) {
+        executeWithErrorLog({ "Failed call unregisterRecentTasksListener" }) {
+            recentTasks?.unregisterRecentTasksListener(listener)
+        }
+        recentTasksListener = null
+    }
+
+    //
+    // Back navigation transitions
+    //
+    /** Sets the launcher [android.window.IOnBackInvokedCallback] to shell */
+    fun setBackToLauncherCallback(
+        callback: IOnBackInvokedCallback?,
+        runner: IRemoteAnimationRunner?,
+    ) {
+        backToLauncherCallback = callback
+        backToLauncherRunner = runner
+        if (callback == null) return
+        try {
+            backAnimation?.setBackToLauncherCallback(callback, runner)
+        } catch (e: RemoteException) {
+            Log.e(TAG, "Failed call setBackToLauncherCallback", e)
+        } catch (e: SecurityException) {
+            Log.e(TAG, "Failed call setBackToLauncherCallback", e)
+        }
+    }
+
+    /**
+     * Clears the previously registered [IOnBackInvokedCallback].
+     *
+     * @param callback The previously registered callback instance.
+     */
+    fun clearBackToLauncherCallback(callback: IOnBackInvokedCallback) {
+        if (backToLauncherCallback !== callback) {
+            return
+        }
+        backToLauncherCallback = null
+        backToLauncherRunner = null
+        executeWithErrorLog({ "Failed call clearBackToLauncherCallback" }) {
+            backAnimation?.clearBackToLauncherCallback()
+        }
+    }
+
+    /** Called when the status bar color needs to be customized when back navigation. */
+    fun customizeStatusBarAppearance(appearance: AppearanceRegion?) =
+        executeWithErrorLog({ "Failed call customizeStatusBarAppearance" }) {
+            backAnimation?.customizeStatusBarAppearance(appearance)
+        }
+
+    class GetRecentTasksException : Exception {
+        constructor(message: String?) : super(message)
+
+        constructor(message: String?, cause: Throwable?) : super(message, cause)
+    }
+
+    /**
+     * Retrieves a list of Recent tasks from ActivityManager.
+     *
+     * @throws GetRecentTasksException if IRecentTasks is not initialized, or when we get
+     *   RemoteException from server side
+     */
+    @Throws(GetRecentTasksException::class)
+    fun getRecentTasks(numTasks: Int, userId: Int): ArrayList<GroupedTaskInfo> {
+        if (recentTasks == null) {
+            Log.e(TAG, "getRecentTasks() failed due to null mRecentTasks")
+            throw GetRecentTasksException("null mRecentTasks")
+        }
+        try {
+            val rawTasks =
+                recentTasks?.getRecentTasks(
+                    numTasks,
+                    ActivityManager.RECENT_IGNORE_UNAVAILABLE,
+                    userId,
+                ) ?: return ArrayList()
+            return ArrayList(rawTasks.asList())
+        } catch (e: RemoteException) {
+            Log.e(TAG, "Failed call getRecentTasks", e)
+            throw GetRecentTasksException("Failed call getRecentTasks", e)
+        }
+    }
+
+    /** Gets the set of running tasks. */
+    fun getRunningTasks(numTasks: Int): List<RunningTaskInfo> {
+        if (!shouldEnableRunningTasksForDesktopMode()) return emptyList()
+        executeWithErrorLog({ "Failed call getRunningTasks" }) {
+            return recentTasks?.getRunningTasks(numTasks)?.asList() ?: emptyList()
+        }
+        return emptyList()
+    }
+
+    private fun shouldEnableRunningTasksForDesktopMode(): Boolean =
+        DesktopModeStatus.canEnterDesktopMode(context) &&
+            ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS.isTrue
+
+    private fun handleMessageAsync(msg: Message): Boolean {
+        return when (msg.what) {
+            MSG_SET_SHELF_HEIGHT -> {
+                setShelfHeightAsync(msg.arg1, msg.arg2)
+                true
+            }
+
+            MSG_SET_LAUNCHER_KEEP_CLEAR_AREA_HEIGHT -> {
+                setLauncherKeepClearAreaHeight(msg.arg1, msg.arg2)
+                true
+            }
+
+            else -> false
+        }
+    }
+
+    //
+    // Desktop Mode
+    //
+    /** Call shell to show all apps active on the desktop */
+    fun showDesktopApps(displayId: Int, transition: RemoteTransition?) =
+        executeWithErrorLog({ "Failed call showDesktopApps" }) {
+            desktopMode?.showDesktopApps(displayId, transition)
+        }
+
+    /** If task with the given id is on the desktop, bring it to front */
+    fun showDesktopApp(taskId: Int, transition: RemoteTransition?) =
+        executeWithErrorLog({ "Failed call showDesktopApp" }) {
+            desktopMode?.showDesktopApp(taskId, transition)
+        }
+
+    /** Call shell to get number of visible freeform tasks */
+    fun getVisibleDesktopTaskCount(displayId: Int): Int {
+        executeWithErrorLog({ "Failed call getVisibleDesktopTaskCount" }) {
+            return desktopMode?.getVisibleTaskCount(displayId) ?: 0
+        }
+        return 0
+    }
+
+    /** Set a listener on shell to get updates about desktop task state */
+    fun setDesktopTaskListener(listener: IDesktopTaskListener?) {
+        desktopTaskListener = listener
+        executeWithErrorLog({ "Failed call setDesktopTaskListener" }) {
+            desktopMode?.setTaskListener(listener)
+        }
+    }
+
+    /** Perform cleanup transactions after animation to split select is complete */
+    fun onDesktopSplitSelectAnimComplete(taskInfo: RunningTaskInfo?) =
+        executeWithErrorLog({ "Failed call onDesktopSplitSelectAnimComplete" }) {
+            desktopMode?.onDesktopSplitSelectAnimComplete(taskInfo)
+        }
+
+    /** Call shell to move a task with given `taskId` to desktop */
+    fun moveToDesktop(
+        taskId: Int,
+        transitionSource: DesktopModeTransitionSource?,
+        transition: RemoteTransition?,
+    ) =
+        executeWithErrorLog({ "Failed call moveToDesktop" }) {
+            desktopMode?.moveToDesktop(taskId, transitionSource, transition)
+        }
+
+    /** Call shell to remove the desktop that is on given `displayId` */
+    fun removeDesktop(displayId: Int) =
+        executeWithErrorLog({ "Failed call removeDesktop" }) {
+            desktopMode?.removeDesktop(displayId)
+        }
+
+    /** Call shell to move a task with given `taskId` to external display. */
+    fun moveToExternalDisplay(taskId: Int) =
+        executeWithErrorLog({ "Failed call moveToExternalDisplay" }) {
+            desktopMode?.moveToExternalDisplay(taskId)
+        }
+
+    //
+    // Unfold transition
+    //
+    /** Sets the unfold animation lister to sysui. */
+    fun setUnfoldAnimationListener(callback: IUnfoldTransitionListener?) {
+        unfoldAnimationListener = callback
+        executeWithErrorLog({ "Failed call setUnfoldAnimationListener" }) {
+            unfoldAnimation?.setListener(callback)
+        }
+    }
+
+    //
+    // Recents
+    //
+    /** Starts the recents activity. The caller should manage the thread on which this is called. */
+    fun startRecentsActivity(
+        intent: Intent?,
+        options: ActivityOptions,
+        listener: RecentsAnimationListener,
+        useSyntheticRecentsTransition: Boolean,
+    ): Boolean {
+        executeWithErrorLog({ "Error starting recents via shell" }) {
+            recentTasks?.startRecentsTransition(
+                recentsPendingIntent,
+                intent,
+                options.toBundle().apply {
+                    if (useSyntheticRecentsTransition) {
+                        putBoolean("is_synthetic_recents_transition", true)
+                    }
+                },
+                context.iApplicationThread,
+                RecentsAnimationListenerStub(listener),
+            )
+                ?: run {
+                    ActiveGestureProtoLogProxy.logRecentTasksMissing()
+                    return false
+                }
+            return true
+        }
+        return false
+    }
+
+    private class RecentsAnimationListenerStub(val listener: RecentsAnimationListener) :
+        IRecentsAnimationRunner.Stub() {
+        override fun onAnimationStart(
+            controller: IRecentsAnimationController,
+            apps: Array<RemoteAnimationTarget>?,
+            wallpapers: Array<RemoteAnimationTarget>?,
+            homeContentInsets: Rect?,
+            minimizedHomeBounds: Rect?,
+            extras: Bundle?,
+        ) =
+            listener.onAnimationStart(
+                RecentsAnimationControllerCompat(controller),
+                apps,
+                wallpapers,
+                homeContentInsets,
+                minimizedHomeBounds,
+                extras?.apply {
+                    // Aidl bundles need to explicitly set class loader
+                    // https://developer.android.com/guide/components/aidl#Bundles
+                    classLoader = SplitBounds::class.java.classLoader
+                },
+            )
+
+        override fun onAnimationCanceled(taskIds: IntArray?, taskSnapshots: Array<TaskSnapshot>?) =
+            listener.onAnimationCanceled(wrap(taskIds, taskSnapshots))
+
+        override fun onTasksAppeared(apps: Array<RemoteAnimationTarget>?) =
+            listener.onTasksAppeared(apps)
+    }
+
+    //
+    // Drag and drop
+    //
+    /**
+     * For testing purposes. Returns `true` only if the shell drop target has shown and drawn and is
+     * ready to handle drag events and the subsequent drop.
+     */
+    fun isDragAndDropReady(): Boolean {
+        executeWithErrorLog({ "Error querying drag state" }) {
+            return dragAndDrop?.isReadyToHandleDrag ?: false
+        }
+        return false
+    }
+
+    fun dump(pw: PrintWriter) {
+        pw.println("$TAG:")
+
+        pw.println("\tmSystemUiProxy=$systemUiProxy")
+        pw.println("\tmPip=$pip")
+        pw.println("\tmPipAnimationListener=$pipAnimationListener")
+        pw.println("\tmBubbles=$bubbles")
+        pw.println("\tmBubblesListener=$bubblesListener")
+        pw.println("\tmSplitScreen=$splitScreen")
+        pw.println("\tmSplitScreenListener=$splitScreenListener")
+        pw.println("\tmSplitSelectListener=$splitSelectListener")
+        pw.println("\tmOneHanded=$oneHanded")
+        pw.println("\tmShellTransitions=$shellTransitions")
+        pw.println("\tmHomeVisibilityState=" + homeVisibilityState)
+        pw.println("\tmFocusState=" + focusState)
+        pw.println("\tmStartingWindow=$startingWindow")
+        pw.println("\tmStartingWindowListener=$startingWindowListener")
+        pw.println("\tmSysuiUnlockAnimationController=$sysuiUnlockAnimationController")
+        pw.println("\tmLauncherActivityClass=$launcherActivityClass")
+        pw.println("\tmLauncherUnlockAnimationController=$launcherUnlockAnimationController")
+        pw.println("\tmRecentTasks=$recentTasks")
+        pw.println("\tmRecentTasksListener=$recentTasksListener")
+        pw.println("\tmBackAnimation=$backAnimation")
+        pw.println("\tmBackToLauncherCallback=$backToLauncherCallback")
+        pw.println("\tmBackToLauncherRunner=$backToLauncherRunner")
+        pw.println("\tmDesktopMode=$desktopMode")
+        pw.println("\tmDesktopTaskListener=$desktopTaskListener")
+        pw.println("\tmUnfoldAnimation=$unfoldAnimation")
+        pw.println("\tmUnfoldAnimationListener=$unfoldAnimationListener")
+        pw.println("\tmDragAndDrop=$dragAndDrop")
+    }
+
+    /** Adds all interfaces held by this proxy to the bundle */
+    @VisibleForTesting
+    fun addAllInterfaces(out: Bundle) {
+        QuickStepContract.addInterface(systemUiProxy, out)
+        QuickStepContract.addInterface(pip, out)
+        QuickStepContract.addInterface(bubbles, out)
+        QuickStepContract.addInterface(sysuiUnlockAnimationController, out)
+        QuickStepContract.addInterface(splitScreen, out)
+        QuickStepContract.addInterface(oneHanded, out)
+        QuickStepContract.addInterface(shellTransitions, out)
+        QuickStepContract.addInterface(startingWindow, out)
+        QuickStepContract.addInterface(recentTasks, out)
+        QuickStepContract.addInterface(backAnimation, out)
+        QuickStepContract.addInterface(desktopMode, out)
+        QuickStepContract.addInterface(unfoldAnimation, out)
+        QuickStepContract.addInterface(dragAndDrop, out)
+    }
+
+    companion object {
+        private const val TAG = "SystemUiProxy"
+
+        @JvmField val INSTANCE = DaggerSingletonObject(LauncherAppComponent::getSystemUiProxy)
+
+        private const val MSG_SET_SHELF_HEIGHT = 1
+        private const val MSG_SET_LAUNCHER_KEEP_CLEAR_AREA_HEIGHT = 2
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
index 8c26d8f..a315775 100644
--- a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
@@ -51,17 +51,32 @@
     override fun getAllTaskData(forceRefresh: Boolean): Flow<List<Task>> {
         if (forceRefresh) {
             recentsModel.getTasks { result ->
-                tasks.value =
-                    MapForStateFlow(
-                        result
-                            .flatMap { groupTask -> groupTask.tasks }
-                            .associateBy { it.key.id }
-                            .also {
-                                // Clean tasks that are not in the latest group tasks list.
-                                val tasksNoLongerVisible = it.keys.subtract(tasks.value.keys)
-                                removeTasks(tasksNoLongerVisible)
+                val recentTasks =
+                    result
+                        .flatMap { groupTask -> groupTask.tasks }
+                        .associateBy { it.key.id }
+                        .also { hashMap ->
+                            // Clean tasks that are not in the latest group tasks list.
+                            val tasksNoLongerVisible = hashMap.keys.subtract(tasks.value.keys)
+                            removeTasks(tasksNoLongerVisible)
+
+                            // Use pre-loaded thumbnail data and icon from the previous list.
+                            // This reduces the Thumbnail loading time in the Overview and prevent
+                            // empty thumbnail and icon.
+                            val cache =
+                                taskRequests.keys
+                                    .mapNotNull { key ->
+                                        val task = tasks.value[key] ?: return@mapNotNull null
+                                        key to Pair(task.thumbnail, task.icon)
+                                    }
+                                    .toMap()
+
+                            hashMap.values.forEach { task ->
+                                task.thumbnail = task.thumbnail ?: cache[task.key.id]?.first
+                                task.icon = task.icon ?: cache[task.key.id]?.second
                             }
-                    )
+                        }
+                tasks.value = MapForStateFlow(recentTasks)
             }
         }
         return tasks.map { it.values.toList() }
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index a43c686..dbd7273 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -246,13 +246,11 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
-import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
@@ -854,72 +852,6 @@
 
     private int mTaskViewCount = 0;
 
-    private final TaskViewsIterable mTaskViewsIterable = new TaskViewsIterable();
-
-    public class TaskViewsIterable implements Iterable<TaskView> {
-
-        /**
-         * Iterates TaskViews when its index inside the RecentsView is needed.
-         */
-        public void forEachWithIndexInParent(BiConsumer<TaskView, Integer> consumer) {
-            int childCount = getChildCount();
-            for (int index = 0; index < childCount; index++) {
-                if (getChildAt(index) instanceof TaskView taskView) {
-                    consumer.accept(taskView, index);
-                }
-            }
-        }
-
-        @Override
-        public TaskViewsIterator iterator() {
-            return new TaskViewsIterator();
-        }
-    }
-
-    // An Iterator to iterate all the current TaskViews inside the RecentsView.
-    public class TaskViewsIterator implements Iterator<TaskView> {
-        // Refers to the index of the `TaskView` that will be returned when `next()` is called.
-        private int mNextIndex = 0;
-
-        // The "limit" of this iterator. This is the number of children of the RecentsView when
-        // the iterator was created. Adding & removing elements will invalidate the iteration
-        // anyway (and cause next() to throw) so saving this value will guarantee that the
-        // value of hasNext() remains stable and won't flap between true and false when elements
-        // are added and removed from the RecentsView.
-        private final int mLimit = getChildCount();
-
-        TaskViewsIterator() {
-            advanceIfNeeded();
-        }
-
-        @Override
-        public boolean hasNext() {
-            return mNextIndex < mLimit && mNextIndex < getChildCount();
-        }
-
-        @Override
-        public TaskView next() {
-            if (!hasNext()) {
-                throw new IndexOutOfBoundsException(
-                        String.format("mNextIndex: %d, child count: %d", mNextIndex,
-                                getChildCount()));
-            }
-            TaskView taskView = requireTaskViewAt(mNextIndex);
-            mNextIndex++;
-            advanceIfNeeded();
-            return taskView;
-        }
-
-        // Advances `mNextIndex` until it either points to a `TaskView` or to the end of the
-        // Iterator.
-        private void advanceIfNeeded() {
-            while (mNextIndex < mLimit && mNextIndex < getChildCount() && !(getChildAt(
-                    mNextIndex) instanceof TaskView)) {
-                mNextIndex++;
-            }
-        }
-    }
-
     @Nullable
     public TaskView getFirstTaskView() {
         return mUtils.getFirstTaskView();
@@ -2569,7 +2501,7 @@
 
         List<Integer> visibleTaskIds = new ArrayList<>();
         // Update the task data for the in/visible children
-        getTaskViews().forEachWithIndexInParent((taskView, index) -> {
+        getTaskViews().forEachWithIndexInParent((index, taskView) -> {
             List<TaskContainer> containers = taskView.getTaskContainers();
             if (containers.isEmpty()) {
                 return;
@@ -4805,8 +4737,8 @@
     /**
      * Returns iterable [TaskView] children.
      */
-    public TaskViewsIterable getTaskViews() {
-        return mTaskViewsIterable;
+    public RecentsViewUtils.TaskViewsIterable getTaskViews() {
+        return mUtils.getTaskViews();
     }
 
     public void setOnEmptyMessageUpdatedListener(OnEmptyMessageUpdatedListener listener) {
@@ -5239,7 +5171,7 @@
         SplitAnimationTimings timings = AnimUtils.getDeviceOverviewToSplitTimings(
                 mContainer.getDeviceProfile().isTablet);
         if (enableLargeDesktopWindowingTile()) {
-            getTaskViews().forEachWithIndexInParent((taskView, index) -> {
+            getTaskViews().forEachWithIndexInParent((index, taskView) -> {
                 if (taskView instanceof DesktopTaskView) {
                     // Setting pivot to scale down from screen centre.
                     if (isTaskViewVisible(taskView)) {
@@ -6188,7 +6120,7 @@
         }
 
         int lastTaskScroll = getLastTaskScroll(clearAllScroll, clearAllWidth);
-        getTaskViews().forEachWithIndexInParent((taskView, index) -> {
+        getTaskViews().forEachWithIndexInParent((index, taskView) -> {
             float scrollDiff = taskView.getScrollAdjustment(showAsGrid);
             int pageScroll = newPageScrolls[index] + Math.round(scrollDiff);
             if ((mIsRtl && pageScroll < lastTaskScroll)
diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
index ccf22ce..3ac3349 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
+++ b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
@@ -22,12 +22,15 @@
 import com.android.quickstep.util.GroupTask
 import com.android.quickstep.views.RecentsView.RUNNING_TASK_ATTACH_ALPHA
 import com.android.systemui.shared.recents.model.ThumbnailData
+import java.util.function.BiConsumer
 
 /**
  * Helper class for [RecentsView]. This util class contains refactored and extracted functions from
  * RecentsView to facilitate the implementation of unit tests.
  */
 class RecentsViewUtils(private val recentsView: RecentsView<*, *>) {
+    val taskViews = TaskViewsIterable(recentsView)
+
     /** Takes a screenshot of all [taskView] and return map of taskId to the screenshot */
     fun screenshotTasks(taskView: TaskView): Map<Int, ThumbnailData> {
         val recentsAnimationController = recentsView.recentsAnimationController ?: return emptyMap()
@@ -47,27 +50,37 @@
         return otherTasks + desktopTasks
     }
 
+    class TaskViewsIterable(val recentsView: RecentsView<*, *>) : Iterable<TaskView> {
+        /** Iterates TaskViews when its index inside the RecentsView is needed. */
+        fun forEachWithIndexInParent(consumer: BiConsumer<Int, TaskView>) {
+            recentsView.children.forEachIndexed { index, child ->
+                (child as? TaskView)?.let { consumer.accept(index, it) }
+            }
+        }
+
+        override fun iterator(): Iterator<TaskView> =
+            recentsView.children.mapNotNull { it as? TaskView }.iterator()
+    }
+
     /** Counts [TaskView]s that are [DesktopTaskView] instances. */
-    fun getDesktopTaskViewCount(): Int = recentsView.taskViews.count { it is DesktopTaskView }
+    fun getDesktopTaskViewCount(): Int = taskViews.count { it is DesktopTaskView }
 
     /** Returns a list of all large TaskView Ids from [TaskView]s */
-    fun getLargeTaskViewIds(): List<Int> =
-        recentsView.taskViews.filter { it.isLargeTile }.map { it.taskViewId }
+    fun getLargeTaskViewIds(): List<Int> = taskViews.filter { it.isLargeTile }.map { it.taskViewId }
 
     /** Counts [TaskView]s that are large tiles. */
-    fun getLargeTileCount(): Int = recentsView.taskViews.count { it.isLargeTile }
+    fun getLargeTileCount(): Int = taskViews.count { it.isLargeTile }
 
     /** Returns the first TaskView that should be displayed as a large tile. */
     fun getFirstLargeTaskView(): TaskView? =
-        recentsView.taskViews.firstOrNull {
+        taskViews.firstOrNull {
             it.isLargeTile && !(recentsView.isSplitSelectionActive && it is DesktopTaskView)
         }
 
     /** Returns the expected focus task. */
     fun getExpectedFocusedTask(): TaskView? =
-        if (enableLargeDesktopWindowingTile())
-            recentsView.taskViews.firstOrNull { it !is DesktopTaskView }
-        else recentsView.taskViews.firstOrNull()
+        if (enableLargeDesktopWindowingTile()) taskViews.firstOrNull { it !is DesktopTaskView }
+        else taskViews.firstOrNull()
 
     /**
      * Returns the [TaskView] that should be the current page during task binding, in the following
@@ -81,20 +94,20 @@
     fun getExpectedCurrentTask(runningTaskView: TaskView?, focusedTaskView: TaskView?): TaskView? =
         runningTaskView
             ?: focusedTaskView
-            ?: recentsView.taskViews.firstOrNull { it !is DesktopTaskView }
-            ?: recentsView.taskViews.lastOrNull()
+            ?: taskViews.firstOrNull { it !is DesktopTaskView }
+            ?: taskViews.lastOrNull()
 
     /** Returns the first TaskView if it exists, or null otherwise. */
-    fun getFirstTaskView(): TaskView? = recentsView.taskViews.firstOrNull()
+    fun getFirstTaskView(): TaskView? = taskViews.firstOrNull()
 
     /** Returns the last TaskView if it exists, or null otherwise. */
-    fun getLastTaskView(): TaskView? = recentsView.taskViews.lastOrNull()
+    fun getLastTaskView(): TaskView? = taskViews.lastOrNull()
 
     /** Returns the first TaskView that is not large */
-    fun getFirstSmallTaskView(): TaskView? = recentsView.taskViews.firstOrNull { !it.isLargeTile }
+    fun getFirstSmallTaskView(): TaskView? = taskViews.firstOrNull { !it.isLargeTile }
 
     /** Returns the last TaskView that should be displayed as a large tile. */
-    fun getLastLargeTaskView(): TaskView? = recentsView.taskViews.lastOrNull { it.isLargeTile }
+    fun getLastLargeTaskView(): TaskView? = taskViews.lastOrNull { it.isLargeTile }
 
     /**
      * Gets the list of accessibility children. Currently all the children of RecentsViews are
@@ -108,23 +121,23 @@
         nonRunningTaskCarouselHidden: Boolean,
         runningTaskView: TaskView? = recentsView.runningTaskView,
     ): TaskView? =
-        recentsView.taskViews.firstOrNull {
+        taskViews.firstOrNull {
             it.isVisibleInCarousel(runningTaskView, nonRunningTaskCarouselHidden)
         }
 
     /** Returns the last [TaskView], with some tasks possibly hidden in the carousel. */
     fun getLastTaskViewInCarousel(nonRunningTaskCarouselHidden: Boolean): TaskView? =
-        recentsView.taskViews.lastOrNull {
+        taskViews.lastOrNull {
             it.isVisibleInCarousel(recentsView.runningTaskView, nonRunningTaskCarouselHidden)
         }
 
     /** Returns if any small tasks are fully visible */
     fun isAnySmallTaskFullyVisible(): Boolean =
-        recentsView.taskViews.any { !it.isLargeTile && recentsView.isTaskViewFullyVisible(it) }
+        taskViews.any { !it.isLargeTile && recentsView.isTaskViewFullyVisible(it) }
 
     /** Apply attachAlpha to all [TaskView] accordingly to different conditions. */
     fun applyAttachAlpha(nonRunningTaskCarouselHidden: Boolean) {
-        recentsView.taskViews.forEach { taskView ->
+        taskViews.forEach { taskView ->
             taskView.attachAlpha =
                 if (taskView == recentsView.runningTaskView) {
                     RUNNING_TASK_ATTACH_ALPHA.get(recentsView)
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendControllerTest.kt
index cfa12e2..785e585 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendControllerTest.kt
@@ -17,6 +17,7 @@
 package com.android.launcher3.taskbar
 
 import android.animation.AnimatorTestRule
+import androidx.test.core.app.ApplicationProvider
 import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
 import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_DRAGGING
 import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_IN_LAUNCHER
@@ -34,6 +35,10 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.whenever
 
 @RunWith(LauncherMultivalentJUnit::class)
 @EmulatedDevices(["pixelTablet2023"])
@@ -43,11 +48,10 @@
     val context =
         TaskbarWindowSandboxContext.create { builder ->
             builder.bindSystemUiProxy(
-                object : SystemUiProxy(this) {
-                    override fun notifyTaskbarAutohideSuspend(suspend: Boolean) {
-                        super.notifyTaskbarAutohideSuspend(suspend)
-                        latestSuspendNotification = suspend
-                    }
+                spy(SystemUiProxy(ApplicationProvider.getApplicationContext())) { proxy ->
+                    doAnswer { latestSuspendNotification = it.getArgument(0) }
+                        .whenever(proxy)
+                        .notifyTaskbarAutohideSuspend(anyOrNull())
                 }
             )
         }
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
index eb13b55..36e8a82 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
@@ -21,6 +21,7 @@
 import android.content.Intent
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.SetFlagsRule
+import androidx.test.core.app.ApplicationProvider
 import com.android.launcher3.Flags.FLAG_TASKBAR_OVERFLOW
 import com.android.launcher3.R
 import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync
@@ -50,6 +51,10 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.whenever
 
 @RunWith(LauncherMultivalentJUnit::class)
 @EmulatedDevices(["pixelTablet2023"])
@@ -66,10 +71,10 @@
     val context =
         TaskbarWindowSandboxContext.create { builder ->
             builder.bindSystemUiProxy(
-                object : SystemUiProxy(this) {
-                    override fun setDesktopTaskListener(listener: IDesktopTaskListener?) {
-                        desktopTaskListener = listener
-                    }
+                spy(SystemUiProxy(ApplicationProvider.getApplicationContext())) { proxy ->
+                    doAnswer { desktopTaskListener = it.getArgument(0) }
+                        .whenever(proxy)
+                        .setDesktopTaskListener(anyOrNull())
                 }
             )
         }
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarScrimViewControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarScrimViewControllerTest.kt
index 4c94067..360f019 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarScrimViewControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarScrimViewControllerTest.kt
@@ -20,9 +20,9 @@
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.SetFlagsRule
-import android.view.KeyEvent
 import android.view.View.GONE
 import android.view.View.VISIBLE
+import androidx.test.core.app.ApplicationProvider
 import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
 import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController
 import com.android.launcher3.taskbar.rules.TaskbarModeRule
@@ -44,6 +44,10 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.whenever
 
 @RunWith(LauncherMultivalentJUnit::class)
 @EmulatedDevices(["pixelTablet2023"])
@@ -53,11 +57,8 @@
     val context =
         TaskbarWindowSandboxContext.create { builder ->
             builder.bindSystemUiProxy(
-                object : SystemUiProxy(this) {
-                    override fun onBackEvent(backEvent: KeyEvent?) {
-                        super.onBackEvent(backEvent)
-                        backPressed = true
-                    }
+                spy(SystemUiProxy(ApplicationProvider.getApplicationContext())) {
+                    doAnswer { backPressed = true }.whenever(it).onBackEvent(anyOrNull())
                 }
             )
         }
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt
index 5e438bd..588c22c 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt
@@ -56,8 +56,8 @@
 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING
 import com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_BAR
 import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.TruthJUnit.assume
 import org.junit.After
-import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -82,11 +82,6 @@
 
     private val activityContext by taskbarUnitTestRule::activityContext
 
-    // Disable hardware keyboard mode during tests.
-    @Before fun enableSoftwareIme() = TaskbarStashController.enableSoftwareImeForTests(true)
-
-    @After fun resetIme() = TaskbarStashController.enableSoftwareImeForTests(false)
-
     @After fun cancelTimeoutIfExists() = stashController.cancelTimeoutIfExists()
 
     @Test
@@ -544,6 +539,8 @@
     @Test
     @TaskbarMode(PINNED)
     fun testAnimatePinnedTaskbar_imeShown_replacesIconsWithHandle() {
+        assume().that(activityContext.isHardwareKeyboard).isFalse()
+
         getInstrumentation().runOnMainSync {
             stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_SHOWING, false)
             animatorTestRule.advanceTimeBy(TASKBAR_STASH_DURATION_FOR_IME)
@@ -555,6 +552,8 @@
     @Test
     @TaskbarMode(PINNED)
     fun testAnimatePinnedTaskbar_imeHidden_replacesHandleWithIcons() {
+        assume().that(activityContext.isHardwareKeyboard).isFalse()
+
         getInstrumentation().runOnMainSync {
             stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_SHOWING, true)
             animatorTestRule.advanceTimeBy(0)
@@ -571,6 +570,8 @@
     @Test
     @TaskbarMode(PINNED)
     fun testAnimatePinnedTaskbar_imeHidden_verifyAnimationDuration() {
+        assume().that(activityContext.isHardwareKeyboard).isFalse()
+
         // Start with IME shown.
         getInstrumentation().runOnMainSync {
             stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_SHOWING, true)
@@ -596,6 +597,8 @@
     @Test
     @TaskbarMode(THREE_BUTTONS)
     fun testAnimateThreeButtonsTaskbar_imeShown_hidesIconsAndBg() {
+        assume().that(activityContext.isHardwareKeyboard).isFalse()
+
         getInstrumentation().runOnMainSync {
             stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_SHOWING, false)
             animatorTestRule.advanceTimeBy(TASKBAR_STASH_DURATION_FOR_IME)
@@ -607,6 +610,8 @@
     @Test
     @TaskbarMode(THREE_BUTTONS)
     fun testAnimateThreeButtonsTaskbar_imeHidden_showsIconsAndBg() {
+        assume().that(activityContext.isHardwareKeyboard).isFalse()
+
         getInstrumentation().runOnMainSync {
             stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_SHOWING, false)
             animatorTestRule.advanceTimeBy(TASKBAR_STASH_DURATION_FOR_IME)
@@ -625,6 +630,8 @@
     @Test
     @TaskbarMode(PINNED)
     fun testSetSystemGestureInProgress_whileImeShown_unstashesTaskbar() {
+        assume().that(activityContext.isHardwareKeyboard).isFalse()
+
         getInstrumentation().runOnMainSync {
             stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_SHOWING, true)
             animatorTestRule.advanceTimeBy(0)
@@ -641,6 +648,19 @@
 
     @Test
     @TaskbarMode(PINNED)
+    fun testSysuiStateImeShowingInApp_hardwareKeyboardWithPinnedMode_notStashedForIme() {
+        assume().that(activityContext.isHardwareKeyboard).isTrue()
+
+        getInstrumentation().runOnMainSync {
+            stashController.updateStateForFlag(FLAG_IN_APP, true)
+            stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_SHOWING, true)
+        }
+
+        assertThat(stashController.isStashed).isFalse()
+    }
+
+    @Test
+    @TaskbarMode(PINNED)
     fun testUnlockTransition_pinnedMode_fadesOutHandle() {
         getInstrumentation().runOnMainSync {
             stashController.updateStateForFlag(FLAG_STASHED_DEVICE_LOCKED, true)
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentTasksDataSource.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentTasksDataSource.kt
index eaeb513..9e99a0b 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentTasksDataSource.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentTasksDataSource.kt
@@ -20,10 +20,12 @@
 import java.util.function.Consumer
 
 class FakeRecentTasksDataSource : RecentTasksDataSource {
-    var taskList: List<GroupTask> = listOf()
+    private var taskList: List<GroupTask> = listOf()
 
     override fun getTasks(callback: Consumer<List<GroupTask>>?): Int {
-        callback?.accept(taskList)
+        // Makes a copy of the GroupTask to create a new GroupTask instance and to simulate
+        // RecentsModel::getTasks behavior.
+        callback?.accept(taskList.map { it.copy() })
         return 0
     }
 
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
index e0e7f28..624310b 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
@@ -117,6 +117,20 @@
         }
 
     @Test
+    fun whenThumbnailIsLoaded_getAllTaskData_usesPreviousLoadedThumbnailAndIcon() =
+        testScope.runTest {
+            recentsModel.seedTasks(defaultTaskList)
+            systemUnderTest.getAllTaskData(forceRefresh = true)
+            val bitmap1 = taskThumbnailDataSource.taskIdToBitmap[1]
+
+            systemUnderTest.setVisibleTasks(setOf(1))
+            assertThat(systemUnderTest.getThumbnailById(1).first()!!.thumbnail).isEqualTo(bitmap1)
+
+            systemUnderTest.getAllTaskData(forceRefresh = true)
+            assertThat(systemUnderTest.getThumbnailById(1).first()!!.thumbnail).isEqualTo(bitmap1)
+        }
+
+    @Test
     fun getCurrentThumbnailByIdReturnsThumbnailWithLoadedThumbnails() =
         testScope.runTest {
             recentsModel.seedTasks(defaultTaskList)
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 94b1a2a..c37b56c 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -869,7 +869,6 @@
             case REQUEST_CREATE_SHORTCUT:
                 completeAddShortcut(intent, info.container, screenId,
                         cellPos.cellX, cellPos.cellY, info);
-                announceForAccessibility(R.string.item_added_to_workspace);
                 break;
             case REQUEST_CREATE_APPWIDGET:
                 completeAddAppWidget(appWidgetId, info, null, null, false, true, null);
diff --git a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
index 7367f2e..eaca6c5 100644
--- a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
+++ b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
@@ -139,7 +139,7 @@
 
         switch (path) {
             case KEY_SHAPE_OPTIONS: {
-                if (Flags.newCustomizationPickerUi()) {
+                if (Flags.newCustomizationPickerUi() && Flags.enableLauncherIconShapes()) {
                     MatrixCursor cursor = new MatrixCursor(new String[]{
                             KEY_SHAPE_KEY, KEY_SHAPE_TITLE, KEY_PATH, KEY_IS_DEFAULT});
                     List<AppShape> shapes =  AppShapesProvider.INSTANCE.getShapes();
@@ -355,7 +355,7 @@
                     renderer.hideBottomRow(message.getData().getBoolean(KEY_HIDE_BOTTOM_ROW));
                     break;
                 case MESSAGE_ID_UPDATE_SHAPE:
-                    if (Flags.newCustomizationPickerUi()) {
+                    if (Flags.newCustomizationPickerUi() && Flags.enableLauncherIconShapes()) {
                         String shapeKey = message.getData().getString(KEY_SHAPE_KEY);
                         Optional<AppShape> optionalShape = AppShapesProvider.INSTANCE.getShapes()
                                 .stream()