Merge "Waiting for wallpaper animation completion when configuring new widget" into main
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 760d8ac..9080284 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -76,3 +76,10 @@
     description: "Enables logging of Launcher restore metrics to the Backup & Restore team"
     bug: "307527314"
 }
+
+flag {
+    name: "enable_unfolded_two_pane_picker"
+    namespace: "launcher"
+    description: "Enables two pane widget picker for unfolded foldables"
+    bug: "313922374"
+}
diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto
index 5a9e147..8240f11 100644
--- a/protos/launcher_atom.proto
+++ b/protos/launcher_atom.proto
@@ -138,7 +138,7 @@
   }
 }
 
-// Next value 54
+// Next value 55
 enum Attribute {
   option allow_alias = true;
 
@@ -200,6 +200,7 @@
   DATA_SOURCE_APPSEARCH_CATEGORY_SRP_PREVIEW = 48;
   DATA_SOURCE_APPSEARCH_ENTITY_SRP_PREVIEW = 49;
   DATA_SOURCE_AIAI_SEARCH_ROOT = 47;
+  DATA_SOURCE_LAUNCHER = 54;
 
   // Web suggestions provided by AGA
   ALL_APPS_SEARCH_RESULT_WEB_SUGGEST = 39;
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
index cbb991d..b29ce6b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
@@ -133,11 +133,20 @@
         GroupTask task = mControllerCallbacks.getTaskAt(index);
         if (task == null) {
             return Math.max(0, index);
-        } else if (mOnDesktop) {
+        }
+        Task task2 = task.task2;
+        int runningTaskId = ActivityManagerWrapper.getInstance().getRunningTask().taskId;
+        if (runningTaskId == task.task1.key.id
+                || (task2 != null && runningTaskId == task2.key.id)) {
+            // Ignore attempts to run the selected task if it is already running.
+            return -1;
+        }
+
+        if (mOnDesktop) {
             UI_HELPER_EXECUTOR.execute(() ->
                     SystemUiProxy.INSTANCE.get(mKeyboardQuickSwitchView.getContext())
                             .showDesktopApp(task.task1.key.id));
-        } else if (task.task2 == null) {
+        } else if (task2 == null) {
             UI_HELPER_EXECUTOR.execute(() ->
                     ActivityManagerWrapper.getInstance().startActivityFromRecents(
                             task.task1.key,
@@ -145,8 +154,7 @@
                                     taskView == null ? mKeyboardQuickSwitchView : taskView, null)
                                     .options));
         } else {
-            mControllers.uiController.launchSplitTasks(
-                    taskView == null ? mKeyboardQuickSwitchView : taskView, task);
+            mControllers.uiController.launchSplitTasks(task);
         }
         return -1;
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index bbe73ff..b4754c6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -26,7 +26,6 @@
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.TaskTransitionSpec;
-import android.view.View;
 import android.view.WindowManagerGlobal;
 
 import androidx.annotation.NonNull;
@@ -386,8 +385,8 @@
     }
 
     @Override
-    public void launchSplitTasks(@NonNull View taskView, @NonNull GroupTask groupTask) {
-        mLauncher.launchSplitTasks(taskView, groupTask);
+    public void launchSplitTasks(@NonNull GroupTask groupTask) {
+        mLauncher.launchSplitTasks(groupTask);
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 988ef80..60ee38f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -203,11 +203,16 @@
         Display display = windowContext.getDisplay();
         Context c = getApplicationContext();
         mWindowManager = c.getSystemService(WindowManager.class);
-        mLeftCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
-        mRightCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT);
+
+        boolean phoneMode = TaskbarManager.isPhoneMode(mDeviceProfile);
+        mLeftCorner = phoneMode
+                ? null
+                : display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
+        mRightCorner = phoneMode
+                ? null
+                : display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT);
 
         // Inflate views.
-        boolean phoneMode = TaskbarManager.isPhoneMode(mDeviceProfile);
         int taskbarLayout = DisplayController.isTransientTaskbar(this) && !phoneMode
                 ? R.layout.transient_taskbar
                 : R.layout.taskbar;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index 445b312..aee3c6f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -297,11 +297,9 @@
     }
 
     /**
-     * Launches the focused task in splitscreen.
-     *
-     * No-op if the view is not yet open.
+     * Launches the given task in split-screen.
      */
-    public void launchSplitTasks(@NonNull View taskview, @NonNull GroupTask groupTask) { }
+    public void launchSplitTasks(@NonNull GroupTask groupTask) { }
 
     /**
      * Returns the matching view (if any) in the taskbar.
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 5b0c8c3..89b7fa4 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -19,6 +19,7 @@
 import static android.os.Trace.TRACE_TAG_APP;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
+
 import static com.android.app.animation.Interpolators.EMPHASIZED;
 import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.PENDING_SPLIT_SELECT_INFO;
 import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE;
@@ -34,8 +35,6 @@
 import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
 import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_HOME_TRANSITION_LISTENER;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
 import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
 import static com.android.launcher3.popup.QuickstepSystemShortcut.getSplitSelectShortcutByPosition;
@@ -1263,24 +1262,19 @@
 
     /**
      * Launches the given {@link GroupTask} in splitscreen.
-     *
-     * If the second split task is missing, launches the first task normally.
      */
-    public void launchSplitTasks(@NonNull View taskView, @NonNull GroupTask groupTask) {
-        if (groupTask.task2 == null) {
-            UI_HELPER_EXECUTOR.execute(() ->
-                    ActivityManagerWrapper.getInstance().startActivityFromRecents(
-                            groupTask.task1.key,
-                            getActivityLaunchOptions(taskView, null).options));
-            return;
-        }
+    public void launchSplitTasks(@NonNull GroupTask groupTask) {
+        // Top/left and bottom/right tasks respectively.
+        Task task1 = groupTask.task1;
+        // task2 should never be null when calling this method. Allow a crash to catch invalid calls
+        Task task2 = groupTask.task2;
         mSplitSelectStateController.launchExistingSplitPair(
                 null /* launchingTaskView */,
-                groupTask.task1.key.id,
-                groupTask.task2.key.id,
+                task1.key.id,
+                task2.key.id,
                 SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
                 /* callback= */ success -> mSplitSelectStateController.resetState(),
-                /* freezeTaskList= */ true,
+                /* freezeTaskList= */ false,
                 groupTask.mSplitBounds == null
                         ? SNAP_TO_50_50
                         : groupTask.mSplitBounds.snapPosition);
diff --git a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
index 5568459..9e58160 100644
--- a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
+++ b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
@@ -55,6 +55,7 @@
 import com.android.launcher3.QuickstepTransitionManager;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.taskbar.LauncherTaskbarUIController;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.quickstep.util.RectFSpringAnim;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -416,6 +417,10 @@
         if (mLauncher.isDestroyed()) {
             return;
         }
+        LauncherTaskbarUIController taskbarUIController = mLauncher.getTaskbarUIController();
+        if (taskbarUIController != null) {
+            taskbarUIController.onLauncherVisibilityChanged(true);
+        }
         // TODO: Catch the moment when launcher becomes visible after the top app un-occludes
         //  launcher and start animating afterwards. Currently we occasionally get a flicker from
         //  animating when launcher is still invisible.
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
new file mode 100644
index 0000000..27de20c
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -0,0 +1,1515 @@
+/*
+ * 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.testing.shared.TestProtocol.testLogD;
+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.ActiveGestureErrorDetector.GestureEvent.RECENT_TASKS_MISSING;
+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.PackageManager;
+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.IRecentsAnimationController;
+import android.view.IRecentsAnimationRunner;
+import android.view.IRemoteAnimationRunner;
+import android.view.MotionEvent;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+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.WorkerThread;
+
+import com.android.internal.logging.InstanceId;
+import com.android.internal.util.ScreenshotRequest;
+import com.android.internal.view.AppearanceRegion;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.Preconditions;
+import com.android.quickstep.util.ActiveGestureLog;
+import com.android.quickstep.util.AssistUtils;
+import com.android.systemui.shared.recents.ISystemUiProxy;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+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.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.split.SplitScreenConstants.PersistentSnapPosition;
+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.pip.IPip;
+import com.android.wm.shell.pip.IPipAnimationListener;
+import com.android.wm.shell.recents.IRecentTasks;
+import com.android.wm.shell.recents.IRecentTasksListener;
+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 com.android.wm.shell.transition.IHomeTransitionListener;
+import com.android.wm.shell.transition.IShellTransitions;
+import com.android.wm.shell.util.GroupedRecentTaskInfo;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+
+/**
+ * Holds the reference to SystemUI.
+ */
+public class SystemUiProxy implements ISystemUiProxy {
+    private static final String TAG = SystemUiProxy.class.getSimpleName();
+
+    public static final MainThreadInitializedObject<SystemUiProxy> INSTANCE =
+            new MainThreadInitializedObject<>(SystemUiProxy::new);
+
+    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 IBinder mOriginalTransactionToken = null;
+    private IOnBackInvokedCallback mBackToLauncherCallback;
+    private IRemoteAnimationRunner mBackToLauncherRunner;
+    private IDragAndDrop mDragAndDrop;
+    private IHomeTransitionListener mHomeTransitionListener;
+
+    // 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
+    private int 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;
+
+    public SystemUiProxy(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());
+    }
+
+    @Override
+    public void onBackPressed() {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.onBackPressed();
+            } 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 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 = 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);
+        setHomeTransitionListener(mHomeTransitionListener);
+        setStartingWindowListener(mStartingWindowListener);
+        setLauncherUnlockAnimationController(
+                mLauncherActivityClass, mLauncherUnlockAnimationController);
+        new LinkedHashMap<>(mRemoteTransitions).forEach(this::registerRemoteTransition);
+        setupTransactionQueue();
+        registerRecentTasksListener(mRecentTasksListener);
+        setBackToLauncherCallback(mBackToLauncherCallback, mBackToLauncherRunner);
+        setUnfoldAnimationListener(mUnfoldAnimationListener);
+        setDesktopTaskListener(mDesktopTaskListener);
+        setAssistantOverridesRequested(
+                AssistUtils.newInstance(mContext).getSysUiAssistOverrideInvocationTypes());
+    }
+
+    /**
+     * 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);
+    }
+
+    // TODO(141886704): Find a way to remove this
+    public void setLastSystemUiStateFlags(int stateFlags) {
+        mLastSystemUiStateFlags = stateFlags;
+    }
+
+    // TODO(141886704): Find a way to remove this
+    public int 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 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);
+            }
+        }
+    }
+
+    //
+    // 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) {
+        if (mPip != null) {
+            try {
+                mPip.stopSwipePipToHome(taskId, componentName, destinationBounds, overlay);
+            } 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 bubbleBarOffsetX the offset of the bubble bar from the edge of the screen on the X
+     *                         axis.
+     * @param bubbleBarOffsetY the offset of the bubble bar from the edge of the screen on the Y
+     *                         axis.
+     */
+    public void showBubble(String key, int bubbleBarOffsetX, int bubbleBarOffsetY) {
+        if (mBubbles != null) {
+            try {
+                mBubbles.showBubble(key, bubbleBarOffsetX, bubbleBarOffsetY);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call showBubble");
+            }
+        }
+    }
+
+    /**
+     * Tells SysUI to remove the bubble with the provided key.
+     * @param key the key of the bubble to show.
+     */
+    public void removeBubble(String key) {
+        if (mBubbles == null) return;
+        try {
+            mBubbles.removeBubble(key);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed call removeBubble");
+        }
+    }
+
+    /**
+     * 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 the key of the bubble to collapse/expand
+     * @param isBeingDragged whether the bubble is being dragged
+     */
+    public void onBubbleDrag(@Nullable String bubbleKey, boolean isBeingDragged) {
+        if (mBubbles == null) return;
+        try {
+            mBubbles.onBubbleDrag(bubbleKey, isBeingDragged);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed call onBubbleDrag");
+        }
+    }
+
+    /**
+     * 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");
+        }
+    }
+
+    //
+    // 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);
+            }
+        }
+    }
+
+    /**
+     * Start multiple tasks in split-screen simultaneously.
+     */
+    public void startTasksWithLegacyTransition(int taskId1, Bundle options1, int taskId2,
+            Bundle options2, @StagePosition int splitPosition,
+            @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
+            InstanceId instanceId) {
+        if (mSystemUiProxy != null) {
+            try {
+                mSplitScreen.startTasksWithLegacyTransition(taskId1, options1, taskId2, options2,
+                        splitPosition, snapPosition, adapter, instanceId);
+            } catch (RemoteException e) {
+                Log.w(TAG, splitFailureMessage(
+                        "startTasksWithLegacyTransition", "RemoteException"), e);
+            }
+        }
+    }
+
+    public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, int userId1,
+            Bundle options1, int taskId, Bundle options2, @StagePosition int splitPosition,
+            @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
+            InstanceId instanceId) {
+        if (mSystemUiProxy != null) {
+            try {
+                mSplitScreen.startIntentAndTaskWithLegacyTransition(pendingIntent, userId1,
+                        options1, taskId, options2, splitPosition, snapPosition, adapter,
+                        instanceId);
+            } catch (RemoteException e) {
+                Log.w(TAG, splitFailureMessage(
+                        "startIntentAndTaskWithLegacyTransition", "RemoteException"), e);
+            }
+        }
+    }
+
+    public void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo, Bundle options1,
+            int taskId, Bundle options2, @StagePosition int splitPosition,
+            @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
+            InstanceId instanceId) {
+        if (mSystemUiProxy != null) {
+            try {
+                mSplitScreen.startShortcutAndTaskWithLegacyTransition(shortcutInfo, options1,
+                        taskId, options2, splitPosition, snapPosition, adapter, instanceId);
+            } catch (RemoteException e) {
+                Log.w(TAG, splitFailureMessage(
+                        "startShortcutAndTaskWithLegacyTransition", "RemoteException"), e);
+            }
+        }
+    }
+
+    /**
+     * Starts a pair of intents or shortcuts in split-screen using legacy transition. Passing a
+     * non-null shortcut info means to start the app as a shortcut.
+     */
+    public void startIntentsWithLegacyTransition(PendingIntent pendingIntent1, int userId1,
+            @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
+            PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2,
+            @Nullable Bundle options2, @StagePosition int sidePosition,
+            @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
+            InstanceId instanceId) {
+        if (mSystemUiProxy != null) {
+            try {
+                mSplitScreen.startIntentsWithLegacyTransition(pendingIntent1, userId1,
+                        shortcutInfo1, options1, pendingIntent2, userId2, shortcutInfo2, options2,
+                        sidePosition, snapPosition, adapter, instanceId);
+            } catch (RemoteException e) {
+                Log.w(TAG, splitFailureMessage(
+                        "startIntentsWithLegacyTransition", "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);
+            }
+        }
+    }
+
+    public void removeFromSideStage(int taskId) {
+        if (mSplitScreen != null) {
+            try {
+                mSplitScreen.removeFromSideStage(taskId);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call removeFromSideStage");
+            }
+        }
+    }
+
+    /**
+     * Call this when going to recents so that shell can set-up and provide appropriate leashes
+     * for animation (eg. DividerBar).
+     *
+     * @return RemoteAnimationTargets of windows that need to animate but only exist in shell.
+     */
+    @Nullable
+    public RemoteAnimationTarget[] onGoingToRecentsLegacy(RemoteAnimationTarget[] apps) {
+        if (!TaskAnimationManager.ENABLE_SHELL_TRANSITIONS && mSplitScreen != null) {
+            try {
+                return mSplitScreen.onGoingToRecentsLegacy(apps);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call onGoingToRecentsLegacy");
+            }
+        }
+        return null;
+    }
+
+    @Nullable
+    public RemoteAnimationTarget[] onStartingSplitLegacy(RemoteAnimationTarget[] apps) {
+        if (mSplitScreen != null) {
+            try {
+                return mSplitScreen.onStartingSplitLegacy(apps);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call onStartingSplitLegacy");
+            }
+        }
+        return null;
+    }
+
+    //
+    // 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 void setHomeTransitionListener(IHomeTransitionListener listener) {
+        if (!FeatureFlags.enableHomeTransitionListener()) {
+            return;
+        }
+
+        mHomeTransitionListener = listener;
+
+        if (mShellTransitions != null) {
+            try {
+                mShellTransitions.setHomeTransitionListener(listener);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call setHomeTransitionListener", e);
+            }
+        } else  {
+            Log.w(TAG, "Unable to call setHomeTransitionListener because ShellTransitions is 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 ArrayList<GroupedRecentTaskInfo> getRecentTasks(int numTasks, int userId) {
+        if (mRecentTasks == null) {
+            Log.w(TAG, "getRecentTasks() failed due to null mRecentTasks");
+            return new ArrayList<>();
+        }
+        try {
+            final GroupedRecentTaskInfo[] rawTasks = mRecentTasks.getRecentTasks(numTasks,
+                    RECENT_IGNORE_UNAVAILABLE, userId);
+            if (rawTasks == null) {
+                return new ArrayList<>();
+            }
+            return new ArrayList<>(Arrays.asList(rawTasks));
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed call getRecentTasks", e);
+            return new ArrayList<>();
+        }
+    }
+
+    /**
+     * Gets the set of running tasks.
+     */
+    public ArrayList<ActivityManager.RunningTaskInfo> getRunningTasks(int numTasks) {
+        if (mRecentTasks != null
+                && mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_PC)) {
+            try {
+                return new ArrayList<>(Arrays.asList(mRecentTasks.getRunningTasks(numTasks)));
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call getRunningTasks", e);
+            }
+        }
+        return new ArrayList<>();
+    }
+
+    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);
+            }
+        }
+    }
+
+    /** Call shell to stash desktop apps */
+    public void stashDesktopApps(int displayId) {
+        if (mDesktopMode != null) {
+            try {
+                mDesktopMode.stashDesktopApps(displayId);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call stashDesktopApps", e);
+            }
+        }
+    }
+
+    /** Call shell to hide desktop apps that may be stashed */
+    public void hideStashedDesktopApps(int displayId) {
+        if (mDesktopMode != null) {
+            try {
+                mDesktopMode.hideStashedDesktopApps(displayId);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call hideStashedDesktopApps", e);
+            }
+        }
+    }
+
+    /**
+     * If task with the given id is on the desktop, bring it to front
+     */
+    public void showDesktopApp(int taskId) {
+        if (mDesktopMode != null) {
+            try {
+                mDesktopMode.showDesktopApp(taskId);
+            } 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);
+            }
+        }
+    }
+
+    //
+    // 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);
+        }
+    }
+
+    //
+    // 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) {
+        if (mRecentTasks == null) {
+            ActiveGestureLog.INSTANCE.addLog("Null mRecentTasks", RECENT_TASKS_MISSING);
+            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(getClass().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();
+        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("\tmHomeTransitionListener=" + mHomeTransitionListener);
+        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);
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.kt b/quickstep/src/com/android/quickstep/SystemUiProxy.kt
deleted file mode 100644
index c70642a..0000000
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.kt
+++ /dev/null
@@ -1,1232 +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 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.PackageManager
-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.IRecentsAnimationController
-import android.view.IRecentsAnimationRunner
-import android.view.IRemoteAnimationRunner
-import android.view.MotionEvent
-import android.view.RemoteAnimationAdapter
-import android.view.RemoteAnimationTarget
-import android.view.SurfaceControl
-import android.window.IOnBackInvokedCallback
-import android.window.RemoteTransition
-import android.window.TaskSnapshot
-import android.window.TransitionFilter
-import androidx.annotation.MainThread
-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.config.FeatureFlags
-import com.android.launcher3.util.Executors
-import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
-import com.android.launcher3.util.MainThreadInitializedObject
-import com.android.launcher3.util.Preconditions
-import com.android.launcher3.util.SplitConfigurationOptions.StagePosition
-import com.android.quickstep.util.ActiveGestureErrorDetector
-import com.android.quickstep.util.ActiveGestureLog
-import com.android.quickstep.util.AssistUtils
-import com.android.quickstep.util.LogUtils.splitFailureMessage
-import com.android.systemui.shared.recents.ISystemUiProxy
-import com.android.systemui.shared.recents.model.ThumbnailData
-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.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.split.SplitScreenConstants.PersistentSnapPosition
-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.pip.IPip
-import com.android.wm.shell.pip.IPipAnimationListener
-import com.android.wm.shell.recents.IRecentTasks
-import com.android.wm.shell.recents.IRecentTasksListener
-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 com.android.wm.shell.transition.IHomeTransitionListener
-import com.android.wm.shell.transition.IShellTransitions
-import com.android.wm.shell.util.GroupedRecentTaskInfo
-import java.io.PrintWriter
-
-/** Holds the reference to SystemUI. */
-class SystemUiProxy(private val context: Context) {
-
-    private val asyncHandler = Handler(UI_HELPER_EXECUTOR.looper, this::handleMessageAsync)
-
-    /**
-     * 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()
-        )
-
-    private val systemUiProxyDeathRecipient =
-        IBinder.DeathRecipient { Executors.MAIN_EXECUTOR.execute { clearProxy() } }
-
-    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
-
-    // 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 var originalTransactionToken: IBinder? = null
-    private var backToLauncherCallback: IOnBackInvokedCallback? = null
-    private var backToLauncherRunner: IRemoteAnimationRunner? = null
-    private var dragAndDrop: IDragAndDrop? = null
-    private var homeTransitionListener: IHomeTransitionListener? = null
-
-    // 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
-
-    // TODO(141886704): Find a way to remove this
-    var lastSystemUiStateFlags = 0
-
-    /**
-     * 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 = 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)
-        setHomeTransitionListener(homeTransitionListener)
-        setStartingWindowListener(startingWindowListener)
-        setLauncherUnlockAnimationController(
-            launcherActivityClass,
-            launcherUnlockAnimationController
-        )
-        LinkedHashMap(remoteTransitions).forEach(this::registerRemoteTransition)
-        setupTransactionQueue()
-        registerRecentTasksListener(recentTasksListener)
-        setBackToLauncherCallback(backToLauncherCallback, backToLauncherRunner)
-        setUnfoldAnimationListener(unfoldAnimationListener)
-        setDesktopTaskListener(desktopTaskListener)
-        setAssistantOverridesRequested(
-            AssistUtils.newInstance(context).sysUiAssistOverrideInvocationTypes
-        )
-    }
-
-    /**
-     * 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)
-    }
-
-    val isActive: Boolean
-        get() = systemUiProxy != null
-
-    private fun linkToDeath() {
-        tryOrLog("Failed to link sysui proxy death recipient") {
-            systemUiProxy?.asBinder()?.linkToDeath(systemUiProxyDeathRecipient, 0 /* flags */)
-        }
-    }
-
-    private fun unlinkToDeath() {
-        systemUiProxy?.asBinder()?.unlinkToDeath(systemUiProxyDeathRecipient, 0 /* flags */)
-    }
-
-    fun onBackPressed() {
-        tryOrLog("Failed call onBackPressed") { systemUiProxy?.onBackPressed() }
-    }
-
-    fun onImeSwitcherPressed() {
-        tryOrLog("Failed call onImeSwitcherPressed") { systemUiProxy?.onImeSwitcherPressed() }
-    }
-
-    fun setHomeRotationEnabled(enabled: Boolean) {
-        tryOrLog("Failed call setHomeRotationEnabled") {
-            systemUiProxy?.setHomeRotationEnabled(enabled)
-        }
-    }
-
-    fun startScreenPinning(taskId: Int) {
-        tryOrLog("Failed call startScreenPinning") { systemUiProxy?.startScreenPinning(taskId) }
-    }
-
-    fun onOverviewShown(fromHome: Boolean, tag: String?) {
-        try {
-            systemUiProxy?.onOverviewShown(fromHome)
-        } catch (e: RemoteException) {
-            Log.w(tag, "Failed call onOverviewShown from: ${if (fromHome) "home" else "app"}", e)
-        }
-    }
-
-    @MainThread
-    fun onStatusBarTouchEvent(event: MotionEvent) {
-        Preconditions.assertUIThread()
-        tryOrLog("Failed call onStatusBarTouchEvent with arg: $event") {
-            systemUiProxy?.onStatusBarTouchEvent(event)
-        }
-    }
-
-    fun onStatusBarTrackpadEvent(event: MotionEvent) {
-        tryOrLog("Failed call onStatusBarTrackpadEvent with arg: $event") {
-            systemUiProxy?.onStatusBarTrackpadEvent(event)
-        }
-    }
-
-    fun onAssistantProgress(progress: Float) {
-        tryOrLog("Failed call onAssistantProgress with progress: $progress") {
-            systemUiProxy?.onAssistantProgress(progress)
-        }
-    }
-
-    fun onAssistantGestureCompletion(velocity: Float) {
-        tryOrLog("Failed call onAssistantGestureCompletion") {
-            systemUiProxy?.onAssistantGestureCompletion(velocity)
-        }
-    }
-
-    fun startAssistant(args: Bundle?) {
-        tryOrLog("Failed call startAssistant") { systemUiProxy?.startAssistant(args) }
-    }
-
-    fun setAssistantOverridesRequested(invocationTypes: IntArray?) {
-        tryOrLog("Failed call setAssistantOverridesRequested") {
-            systemUiProxy?.setAssistantOverridesRequested(invocationTypes)
-        }
-    }
-
-    fun animateNavBarLongPress(isTouchDown: Boolean, shrink:Boolean, durationMs: Long) {
-        tryOrLog("Failed call animateNavBarLongPress") {
-            systemUiProxy?.animateNavBarLongPress(isTouchDown, shrink, durationMs)
-        }
-    }
-
-    fun notifyAccessibilityButtonClicked(displayId: Int) {
-        tryOrLog("Failed call notifyAccessibilityButtonClicked") {
-            systemUiProxy?.notifyAccessibilityButtonClicked(displayId)
-        }
-    }
-
-    fun notifyAccessibilityButtonLongClicked() {
-        tryOrLog("Failed call notifyAccessibilityButtonLongClicked") {
-            systemUiProxy?.notifyAccessibilityButtonLongClicked()
-        }
-    }
-
-    fun stopScreenPinning() {
-        tryOrLog("Failed call stopScreenPinning") { systemUiProxy?.stopScreenPinning() }
-    }
-
-    fun notifyPrioritizedRotation(rotation: Int) {
-        tryOrLog("Failed call notifyPrioritizedRotation with arg: $rotation") {
-            systemUiProxy?.notifyPrioritizedRotation(rotation)
-        }
-    }
-
-    fun notifyTaskbarStatus(visible: Boolean, stashed: Boolean) {
-        tryOrLog("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
-     *
-     * @param suspend should be true to stop auto-hide, false to resume normal behavior
-     */
-    fun notifyTaskbarAutohideSuspend(suspend: Boolean) {
-        tryOrLog("Failed call notifyTaskbarAutohideSuspend with arg: $suspend") {
-            systemUiProxy?.notifyTaskbarAutohideSuspend(suspend)
-        }
-    }
-
-    fun takeScreenshot(request: ScreenshotRequest?) {
-        tryOrLog("Failed call takeScreenshot") { systemUiProxy?.takeScreenshot(request) }
-    }
-
-    fun expandNotificationPanel() {
-        tryOrLog("Failed call expandNotificationPanel") { systemUiProxy?.expandNotificationPanel() }
-    }
-
-    fun toggleNotificationPanel() {
-        tryOrLog("Failed call toggleNotificationPanel") { systemUiProxy?.toggleNotificationPanel() }
-    }
-
-    //
-    // 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
-        if (visible == lastShelfVisible && shelfHeight == lastShelfHeight) return
-
-        pip?.let {
-            tryOrLog("Failed call setShelfHeight visible: $visible height: $shelfHeight") {
-                it.setShelfHeight(visible, shelfHeight)
-                lastShelfVisible = visible
-                lastShelfHeight = 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
-        if (
-            visible == lastLauncherKeepClearAreaHeightVisible &&
-                height == lastLauncherKeepClearAreaHeight
-        )
-            return
-
-        pip?.let {
-            tryOrLog("Failed call setLauncherKeepClearAreaHeight vis: $visible height: $height") {
-                it.setLauncherKeepClearAreaHeight(visible, height)
-                lastLauncherKeepClearAreaHeightVisible = visible
-                lastLauncherKeepClearAreaHeight = height
-            }
-        }
-    }
-
-    /** Sets listener to get pip animation callbacks. */
-    fun setPipAnimationListener(listener: IPipAnimationListener?) {
-        tryOrLog("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? {
-        return tryOrElse("Failed call startSwipePipToHome", null) {
-            return pip?.startSwipePipToHome(
-                componentName,
-                activityInfo,
-                pictureInPictureParams,
-                launcherRotation,
-                hotseatKeepClearArea
-            )
-        }
-    }
-
-    /**
-     * 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?
-    ) {
-        tryOrLog("Failed call stopSwipePipToHome") {
-            pip?.stopSwipePipToHome(taskId, componentName, destinationBounds, overlay)
-        }
-    }
-
-    /**
-     * 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?) {
-        tryOrLog("Failed call abortSwipePipToHome") {
-            pip?.abortSwipePipToHome(taskId, componentName)
-        }
-    }
-
-    /** Sets the next pip animation type to be the alpha animation. */
-    fun setPipAnimationTypeToAlpha() {
-        tryOrLog("Failed call setPipAnimationTypeToAlpha") { pip?.setPipAnimationTypeToAlpha() }
-    }
-
-    /** Sets the app icon size in pixel used by Launcher all apps. */
-    fun setLauncherAppIconSize(iconSizePx: Int) {
-        tryOrLog("Failed call setLauncherAppIconSize") { pip?.setLauncherAppIconSize(iconSizePx) }
-    }
-
-    //
-    // Bubbles
-    //
-    /** Sets the listener to be notified of bubble state changes. */
-    fun setBubblesListener(listener: IBubblesListener?) {
-        bubbles?.let {
-            tryOrLog("Failed call setLauncherAppIconSize") {
-                bubblesListener?.let(it::unregisterBubbleListener)
-                listener?.let(it::registerBubbleListener)
-            }
-        }
-        bubblesListener = listener
-    }
-
-    /**
-     * Tells SysUI to show the bubble with the provided key.
-     *
-     * @param key the key of the bubble to show.
-     * @param bubbleBarOffsetX the offset of the bubble bar from the edge of the screen on the X
-     *   axis.
-     * @param bubbleBarOffsetY the offset of the bubble bar from the edge of the screen on the Y
-     *   axis.
-     */
-    fun showBubble(key: String?, bubbleBarOffsetX: Int, bubbleBarOffsetY: Int) {
-        tryOrLog("Failed call showBubble") {
-            bubbles?.showBubble(key, bubbleBarOffsetX, bubbleBarOffsetY)
-        }
-    }
-
-    /**
-     * Tells SysUI to remove the bubble with the provided key.
-     *
-     * @param key the key of the bubble to show.
-     */
-    fun removeBubble(key: String?) {
-        tryOrLog("Failed call removeBubble") { bubbles?.removeBubble(key) }
-    }
-
-    /** Tells SysUI to remove all bubbles. */
-    fun removeAllBubbles() {
-        tryOrLog("Failed call removeAllBubbles") { bubbles?.removeAllBubbles() }
-    }
-
-    /** Tells SysUI to collapse the bubbles. */
-    fun collapseBubbles() {
-        tryOrLog("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 the key of the bubble to collapse/expand
-     * @param isBeingDragged whether the bubble is being dragged
-     */
-    fun onBubbleDrag(bubbleKey: String?, isBeingDragged: Boolean) {
-        tryOrLog("Failed call onBubbleDrag") { bubbles?.onBubbleDrag(bubbleKey, isBeingDragged) }
-    }
-
-    /**
-     * 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) {
-        tryOrLog("Failed call showUserEducation") {
-            bubbles?.showUserEducation(position.x, position.y)
-        }
-    }
-
-    //
-    // Splitscreen
-    //
-    fun registerSplitScreenListener(listener: ISplitScreenListener?) {
-        tryOrLog("Failed call registerSplitScreenListener") {
-            splitScreen?.registerSplitScreenListener(listener)
-        }
-        splitScreenListener = listener
-    }
-
-    fun registerSplitSelectListener(listener: ISplitSelectListener?) {
-        tryOrLog("Failed call registerSplitScreenListener") {
-            splitScreen?.registerSplitSelectListener(listener)
-        }
-        splitSelectListener = listener
-    }
-
-    /** 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?
-    ) {
-        tryOrLog(splitFailureMessage("startTasks", "RemoteException")) {
-            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?
-    ) {
-        tryOrLog(splitFailureMessage("startIntentAndTask", "RemoteException")) {
-            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?
-    ) {
-        tryOrLog(splitFailureMessage("startIntents", "RemoteException")) {
-            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?
-    ) {
-        tryOrLog(splitFailureMessage("startShortcutAndTask", "RemoteException")) {
-            splitScreen?.startShortcutAndTask(
-                shortcutInfo,
-                options1,
-                taskId,
-                options2,
-                splitPosition,
-                snapPosition,
-                remoteTransition,
-                instanceId
-            )
-        }
-    }
-
-    /** Start multiple tasks in split-screen simultaneously. */
-    fun startTasksWithLegacyTransition(
-        taskId1: Int,
-        options1: Bundle?,
-        taskId2: Int,
-        options2: Bundle?,
-        @StagePosition splitPosition: Int,
-        @PersistentSnapPosition snapPosition: Int,
-        adapter: RemoteAnimationAdapter?,
-        instanceId: InstanceId?
-    ) {
-        tryOrLog(splitFailureMessage("startTasksWithLegacyTransition", "RemoteException")) {
-            splitScreen?.startTasksWithLegacyTransition(
-                taskId1,
-                options1,
-                taskId2,
-                options2,
-                splitPosition,
-                snapPosition,
-                adapter,
-                instanceId
-            )
-        }
-    }
-
-    fun startIntentAndTaskWithLegacyTransition(
-        pendingIntent: PendingIntent?,
-        userId1: Int,
-        options1: Bundle?,
-        taskId: Int,
-        options2: Bundle?,
-        @StagePosition splitPosition: Int,
-        @PersistentSnapPosition snapPosition: Int,
-        adapter: RemoteAnimationAdapter?,
-        instanceId: InstanceId?
-    ) {
-        tryOrLog(splitFailureMessage("startIntentAndTaskWithLegacyTransition", "RemoteException")) {
-            splitScreen?.startIntentAndTaskWithLegacyTransition(
-                pendingIntent,
-                userId1,
-                options1,
-                taskId,
-                options2,
-                splitPosition,
-                snapPosition,
-                adapter,
-                instanceId
-            )
-        }
-    }
-
-    fun startShortcutAndTaskWithLegacyTransition(
-        shortcutInfo: ShortcutInfo?,
-        options1: Bundle?,
-        taskId: Int,
-        options2: Bundle?,
-        @StagePosition splitPosition: Int,
-        @PersistentSnapPosition snapPosition: Int,
-        adapter: RemoteAnimationAdapter?,
-        instanceId: InstanceId?
-    ) {
-        tryOrLog(
-            splitFailureMessage("startShortcutAndTaskWithLegacyTransition", "RemoteException")
-        ) {
-            splitScreen?.startShortcutAndTaskWithLegacyTransition(
-                shortcutInfo,
-                options1,
-                taskId,
-                options2,
-                splitPosition,
-                snapPosition,
-                adapter,
-                instanceId
-            )
-        }
-    }
-
-    /**
-     * Starts a pair of intents or shortcuts in split-screen using legacy transition. Passing a
-     * non-null shortcut info means to start the app as a shortcut.
-     */
-    fun startIntentsWithLegacyTransition(
-        pendingIntent1: PendingIntent?,
-        userId1: Int,
-        shortcutInfo1: ShortcutInfo?,
-        options1: Bundle?,
-        pendingIntent2: PendingIntent?,
-        userId2: Int,
-        shortcutInfo2: ShortcutInfo?,
-        options2: Bundle?,
-        @StagePosition sidePosition: Int,
-        @PersistentSnapPosition snapPosition: Int,
-        adapter: RemoteAnimationAdapter?,
-        instanceId: InstanceId?
-    ) {
-        tryOrLog(splitFailureMessage("startIntentsWithLegacyTransition", "RemoteException")) {
-            splitScreen?.startIntentsWithLegacyTransition(
-                pendingIntent1,
-                userId1,
-                shortcutInfo1,
-                options1,
-                pendingIntent2,
-                userId2,
-                shortcutInfo2,
-                options2,
-                sidePosition,
-                snapPosition,
-                adapter,
-                instanceId
-            )
-        }
-    }
-
-    fun startShortcut(
-        packageName: String?,
-        shortcutId: String?,
-        position: Int,
-        options: Bundle?,
-        user: UserHandle?,
-        instanceId: InstanceId?
-    ) {
-        tryOrLog(splitFailureMessage("startShortcut", "RemoteException")) {
-            splitScreen?.startShortcut(packageName, shortcutId, position, options, user, instanceId)
-        }
-    }
-
-    fun startIntent(
-        intent: PendingIntent?,
-        userId: Int,
-        fillInIntent: Intent?,
-        position: Int,
-        options: Bundle?,
-        instanceId: InstanceId?
-    ) {
-        tryOrLog(splitFailureMessage("startIntent", "RemoteException")) {
-            splitScreen?.startIntent(intent, userId, fillInIntent, position, options, instanceId)
-        }
-    }
-
-    /**
-     * Call this when going to recents so that shell can set-up and provide appropriate leashes for
-     * animation (eg. DividerBar).
-     *
-     * @return RemoteAnimationTargets of windows that need to animate but only exist in shell.
-     */
-    fun onGoingToRecentsLegacy(
-        apps: Array<RemoteAnimationTarget?>?
-    ): Array<RemoteAnimationTarget>? {
-        if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) return null
-        return tryOrElse("Failed call onGoingToRecentsLegacy", null) {
-            return splitScreen?.onGoingToRecentsLegacy(apps)
-        }
-    }
-
-    fun onStartingSplitLegacy(apps: Array<RemoteAnimationTarget?>?): Array<RemoteAnimationTarget>? {
-        return tryOrElse("Failed call onStartingSplitLegacy", null) {
-            return splitScreen?.onStartingSplitLegacy(apps)
-        }
-    }
-
-    //
-    // One handed
-    //
-    fun startOneHandedMode() {
-        tryOrLog("Failed call startOneHandedMode") { oneHanded?.startOneHanded() }
-    }
-
-    fun stopOneHandedMode() {
-        tryOrLog("Failed call startOneHandedstopOneHandedModeMode") { oneHanded?.stopOneHanded() }
-    }
-
-    //
-    // Remote transitions
-    //
-    fun registerRemoteTransition(remoteTransition: RemoteTransition, filter: TransitionFilter) {
-        tryOrLog("Failed call registerRemoteTransition") {
-            shellTransitions?.registerRemote(filter, remoteTransition)
-        }
-        if (!remoteTransitions.containsKey(remoteTransition)) {
-            remoteTransitions[remoteTransition] = filter
-        }
-    }
-
-    fun unregisterRemoteTransition(remoteTransition: RemoteTransition) {
-        tryOrLog("Failed call unregisterRemoteTransition") {
-            shellTransitions?.unregisterRemote(remoteTransition)
-        }
-        remoteTransitions.remove(remoteTransition)
-    }
-
-    fun setHomeTransitionListener(listener: IHomeTransitionListener?) {
-        if (!FeatureFlags.enableHomeTransitionListener()) return
-
-        homeTransitionListener = listener
-
-        tryOrLog("Failed call unregisterRemoteTransition") {
-            shellTransitions?.setHomeTransitionListener(listener)
-                ?: Log.w(
-                    TAG,
-                    "Unable to call setHomeTransitionListener because ShellTransitions is 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 = SurfaceControl.Transaction.getDefaultApplyToken()
-        }
-        setupTransactionQueue()
-    }
-
-    /** Switch back to using Launcher's independent transaction queue. */
-    fun unshareTransactionQueue() {
-        originalTransactionToken?.let { SurfaceControl.Transaction.setDefaultApplyToken(it) }
-        originalTransactionToken = null
-    }
-
-    private fun setupTransactionQueue() {
-        originalTransactionToken ?: return
-
-        var transitions = shellTransitions ?: run {
-            SurfaceControl.Transaction.setDefaultApplyToken(originalTransactionToken)
-            return
-        }
-        tryOrLog("Error getting Shell's apply token") {
-            transitions.getShellApplyToken()?.apply {
-                SurfaceControl.Transaction.setDefaultApplyToken(this)
-            } ?: Log.e(TAG, "Didn't receive apply token from Shell")
-        }
-    }
-
-    //
-    // Starting window
-    //
-    /** Sets listener to get callbacks when launching a task. */
-    fun setStartingWindowListener(listener: IStartingWindowListener?) {
-        tryOrLog("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?
-    ) {
-        tryOrLog("Failed call setLauncherUnlockAnimationController") {
-            sysuiUnlockAnimationController?.let {
-                it.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?) {
-        tryOrLog("Failed call notifySysuiSmartspaceStateUpdated") {
-            sysuiUnlockAnimationController?.onLauncherSmartspaceStateUpdated(state)
-        }
-    }
-
-    //
-    // Recents
-    //
-    fun registerRecentTasksListener(listener: IRecentTasksListener?) {
-        tryOrLog("Failed call registerRecentTasksListener") {
-            recentTasks?.registerRecentTasksListener(listener)
-        }
-        recentTasksListener = listener
-    }
-
-    //
-    // Back navigation transitions
-    //
-    /** Sets the launcher [android.window.IOnBackInvokedCallback] to shell */
-    fun setBackToLauncherCallback(
-        callback: IOnBackInvokedCallback?,
-        runner: IRemoteAnimationRunner?
-    ) {
-        backToLauncherCallback = callback
-        backToLauncherRunner = runner
-
-        callback ?: return
-        tryOrLog("Failed call setBackToLauncherCallback") {
-            backAnimation?.setBackToLauncherCallback(callback, runner)
-        }
-    }
-
-    /**
-     * Clears the previously registered [IOnBackInvokedCallback].
-     *
-     * @param callback The previously registered callback instance.
-     */
-    fun clearBackToLauncherCallback(callback: IOnBackInvokedCallback) {
-        if (backToLauncherCallback != callback) {
-            return
-        }
-        backToLauncherCallback = null
-        backToLauncherRunner = null
-        tryOrLog("Failed call clearBackToLauncherCallback") {
-            backAnimation?.clearBackToLauncherCallback()
-        }
-    }
-
-    /** Called when the status bar color needs to be customized when back navigation. */
-    fun customizeStatusBarAppearance(appearance: AppearanceRegion?) {
-        tryOrLog("Failed call customizeStatusBarAppearance") {
-            backAnimation?.customizeStatusBarAppearance(appearance)
-        }
-    }
-
-    fun getRecentTasks(numTasks: Int, userId: Int): ArrayList<GroupedRecentTaskInfo> {
-        return tryOrElse("Failed call getRecentTasks", ArrayList()) {
-            return recentTasks
-                ?.getRecentTasks(numTasks, ActivityManager.RECENT_IGNORE_UNAVAILABLE, userId)
-                ?.let { ArrayList(it.asList()) }
-                ?: ArrayList()
-        }
-    }
-
-    /** Gets the set of running tasks. */
-    fun getRunningTasks(numTasks: Int): ArrayList<RunningTaskInfo> {
-        if (!context.packageManager.hasSystemFeature(PackageManager.FEATURE_PC)) {
-            return ArrayList()
-        }
-
-        return tryOrElse("Failed call getRunningTasks", ArrayList()) {
-            recentTasks?.getRunningTasks(numTasks)?.let { ArrayList(it.asList()) } ?: ArrayList()
-        }
-    }
-
-    private fun handleMessageAsync(msg: Message): Boolean {
-        when (msg.what) {
-            MSG_SET_SHELF_HEIGHT -> {
-                setShelfHeightAsync(msg.arg1, msg.arg2)
-                return true
-            }
-            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 */
-    fun showDesktopApps(displayId: Int, transition: RemoteTransition?) {
-        tryOrLog("Failed call showDesktopApps") {
-            desktopMode?.showDesktopApps(displayId, transition)
-        }
-    }
-
-    /** Call shell to stash desktop apps */
-    fun stashDesktopApps(displayId: Int) {
-        tryOrLog("Failed call stashDesktopApps") { desktopMode?.stashDesktopApps(displayId) }
-    }
-
-    /** Call shell to hide desktop apps that may be stashed */
-    fun hideStashedDesktopApps(displayId: Int) {
-        tryOrLog("Failed call hideStashedDesktopApps") {
-            desktopMode?.hideStashedDesktopApps(displayId)
-        }
-    }
-
-    /** If task with the given id is on the desktop, bring it to front */
-    fun showDesktopApp(taskId: Int) {
-        tryOrLog("Failed call showDesktopApp") { desktopMode?.showDesktopApp(taskId) }
-    }
-
-    /** Call shell to get number of visible freeform tasks */
-    fun getVisibleDesktopTaskCount(displayId: Int): Int {
-        return tryOrElse("Failed call getVisibleDesktopTaskCount", 0) {
-            return desktopMode?.getVisibleTaskCount(displayId) ?: 0
-        }
-    }
-
-    /** Set a listener on shell to get updates about desktop task state */
-    fun setDesktopTaskListener(listener: IDesktopTaskListener?) {
-        desktopTaskListener = listener
-        tryOrLog("Failed call setDesktopTaskListener") { desktopMode?.setTaskListener(listener) }
-    }
-
-    /** Perform cleanup transactions after animation to split select is complete */
-    fun onDesktopSplitSelectAnimComplete(taskInfo: RunningTaskInfo?) {
-        tryOrLog("Failed call onDesktopSplitSelectAnimComplete") {
-            desktopMode?.onDesktopSplitSelectAnimComplete(taskInfo)
-        }
-    }
-    //
-    // Unfold transition
-    //
-    /** Sets the unfold animation lister to sysui. */
-    fun setUnfoldAnimationListener(callback: IUnfoldTransitionListener?) {
-        unfoldAnimationListener = callback
-        tryOrLog("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
-    ): Boolean {
-
-        val runner: IRecentsAnimationRunner =
-            object : IRecentsAnimationRunner.Stub() {
-                override fun onAnimationStart(
-                    controller: IRecentsAnimationController,
-                    apps: Array<RemoteAnimationTarget>,
-                    wallpapers: Array<RemoteAnimationTarget>,
-                    homeContentInsets: Rect,
-                    minimizedHomeBounds: Rect,
-                    extras: Bundle?
-                ) {
-                    // Aidl bundles need to explicitly set class loader
-                    // https://developer.android.com/guide/components/aidl#Bundles
-                    extras?.classLoader = javaClass.classLoader
-                    listener.onAnimationStart(
-                        RecentsAnimationControllerCompat(controller),
-                        apps,
-                        wallpapers,
-                        homeContentInsets,
-                        minimizedHomeBounds,
-                        extras
-                    )
-                }
-
-                override fun onAnimationCanceled(
-                    taskIds: IntArray,
-                    taskSnapshots: Array<TaskSnapshot>
-                ) {
-                    listener.onAnimationCanceled(ThumbnailData.wrap(taskIds, taskSnapshots))
-                }
-
-                override fun onTasksAppeared(apps: Array<RemoteAnimationTarget>) {
-                    listener.onTasksAppeared(apps)
-                }
-            }
-
-        return tryOrElse("Error starting recents via shell", false) {
-            recentTasks?.let {
-                it.startRecentsTransition(
-                    recentsPendingIntent,
-                    intent,
-                    options.toBundle(),
-                    context.iApplicationThread,
-                    runner
-                )
-                return true
-            }
-                ?: run {
-                    ActiveGestureLog.INSTANCE.addLog(
-                        "Null recentTasks",
-                        ActiveGestureErrorDetector.GestureEvent.RECENT_TASKS_MISSING
-                    )
-                    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.
-     */
-    val isDragAndDropReady: Boolean
-        get() =
-            tryOrElse("Error querying drag state", false) {
-                dragAndDrop?.isReadyToHandleDrag ?: false
-            }
-
-    fun dump(pw: PrintWriter) {
-        pw.println("$TAG:")
-        pw.println("\tsystemUiProxy=$systemUiProxy")
-        pw.println("\tpip=$pip")
-        pw.println("\tpipAnimationListener=$pipAnimationListener")
-        pw.println("\tbubbles=$bubbles")
-        pw.println("\tbubblesListener=$bubblesListener")
-        pw.println("\tsplitScreen=$splitScreen")
-        pw.println("\tsplitScreenListener=$splitScreenListener")
-        pw.println("\tsplitSelectListener=$splitSelectListener")
-        pw.println("\toneHanded=$oneHanded")
-        pw.println("\tshellTransitions=$shellTransitions")
-        pw.println("\thomeTransitionListener=$homeTransitionListener")
-        pw.println("\tstartingWindow=$startingWindow")
-        pw.println("\tstartingWindowListener=$startingWindowListener")
-        pw.println("\tsysuiUnlockAnimationController=$sysuiUnlockAnimationController")
-        pw.println("\tlauncherActivityClass=$launcherActivityClass")
-        pw.println("\tlauncherUnlockAnimationController=$launcherUnlockAnimationController")
-        pw.println("\trecentTasks=$recentTasks")
-        pw.println("\trecentTasksListener=$recentTasksListener")
-        pw.println("\tbackAnimation=$backAnimation")
-        pw.println("\tbackToLauncherCallback=$backToLauncherCallback")
-        pw.println("\tbackToLauncherRunner=$backToLauncherRunner")
-        pw.println("\tdesktopMode=$desktopMode")
-        pw.println("\tdesktopTaskListener=$desktopTaskListener")
-        pw.println("\tunfoldAnimation=$unfoldAnimation")
-        pw.println("\tunfoldAnimationListener=$unfoldAnimationListener")
-        pw.println("\tdragAndDrop=$dragAndDrop")
-    }
-
-    companion object {
-        private const val TAG = "SystemUiProxy"
-        private const val MSG_SET_SHELF_HEIGHT = 1
-        private const val MSG_SET_LAUNCHER_KEEP_CLEAR_AREA_HEIGHT = 2
-
-        @JvmField
-        val INSTANCE = MainThreadInitializedObject { context: Context -> SystemUiProxy(context) }
-
-        private inline fun tryOrLog(msg: String, f: () -> Unit) =
-            try {
-                f()
-            } catch (e: RemoteException) {
-                Log.w(TAG, msg, e)
-            }
-
-        private inline fun <T> tryOrElse(msg: String, fallback: T, f: () -> T): T =
-            try {
-                f()
-            } catch (e: RemoteException) {
-                Log.w(TAG, msg, e)
-                fallback
-            }
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
index e5fca4b..9e21595 100644
--- a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
+++ b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
@@ -195,11 +195,15 @@
             return null;
         }
 
-        CancellableTask<ThumbnailData> request = new CancellableTask<ThumbnailData>() {
+        CancellableTask<ThumbnailData> request = new CancellableTask<>() {
             @Override
             public ThumbnailData getResultOnBg() {
-                return ActivityManagerWrapper.getInstance().getTaskThumbnail(
+                ThumbnailData thumbnailData = ActivityManagerWrapper.getInstance().getTaskThumbnail(
                         key.id, lowResolution);
+                if (thumbnailData.thumbnail != null) {
+                    return thumbnailData;
+                }
+                return ActivityManagerWrapper.getInstance().takeTaskThumbnail(key.id);
             }
 
             @Override
diff --git a/quickstep/src/com/android/quickstep/util/AssistStateManager.java b/quickstep/src/com/android/quickstep/util/AssistStateManager.java
index a0bb76d..f7437eb 100644
--- a/quickstep/src/com/android/quickstep/util/AssistStateManager.java
+++ b/quickstep/src/com/android/quickstep/util/AssistStateManager.java
@@ -31,6 +31,11 @@
 
     public AssistStateManager() {}
 
+    /** Whether search supports haptic on invocation. */
+    public boolean supportsCommitHaptic() {
+        return false;
+    }
+
     /** Whether search is available. */
     public boolean isSearchAvailable() {
         return false;
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt b/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt
index 423ba43..c013483 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt
@@ -93,6 +93,7 @@
     private var secondTaskId: Int = INVALID_TASK_ID
     private var initialIntent: Intent? = null
     private var secondIntent: Intent? = null
+    private var widgetSecondIntent: Intent? = null
     private var initialUser: UserHandle? = null
     private var secondUser: UserHandle? = null
     private var initialPendingIntent: PendingIntent? = null
@@ -167,6 +168,16 @@
         secondUser = pendingIntent.creatorUserHandle
     }
 
+    /**
+     * Similar to [setSecondTask] except this is to be called for widgets which can pass through
+     * an extra intent from their RemoteResponse.
+     * See [android.widget.RemoteViews.RemoteResponse.getLaunchOptions].first
+     */
+    fun setSecondWidget(pendingIntent: PendingIntent, widgetIntent: Intent?) {
+        setSecondTask(pendingIntent)
+        widgetSecondIntent = widgetIntent
+    }
+
     private fun getShortcutInfo(intent: Intent?, user: UserHandle?): ShortcutInfo? {
         val intentPackage = intent?.getPackage() ?: return null
         val shortcutId = intent.getStringExtra(ShortcutKey.EXTRA_SHORTCUT_ID)
@@ -241,6 +252,7 @@
                 secondTaskId,
                 initialPendingIntent,
                 secondPendingIntent,
+                widgetSecondIntent,
                 initialUser?.identifier ?: -1,
                 secondUser?.identifier ?: -1,
                 initialShortcut,
@@ -257,7 +269,8 @@
      * Note that both [initialIntent] and [secondIntent] will be nullified on method return
      *
      * One caveat is that if [secondPendingIntent] is set, we will use that and *not* attempt to
-     * convert [secondIntent]
+     * convert [secondIntent].
+     * This also leaves [widgetSecondIntent] untouched.
      */
     private fun convertIntentsToFinalTypes() {
         initialShortcut = getShortcutInfo(initialIntent, initialUser)
@@ -343,6 +356,7 @@
             var secondTaskId: Int = INVALID_TASK_ID,
             var initialPendingIntent: PendingIntent? = null,
             var secondPendingIntent: PendingIntent? = null,
+            var widgetSecondIntent: Intent? = null,
             var initialUserId: Int = -1,
             var secondUserId: Int = -1,
             var initialShortcut: ShortcutInfo? = null,
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index 145707b..8b27a85 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -32,6 +32,7 @@
 import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_SHORTCUT;
 import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_TASK;
 import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
+import static com.android.wm.shell.common.split.SplitScreenConstants.KEY_EXTRA_WIDGET_INTENT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50;
 
 import android.animation.Animator;
@@ -355,6 +356,10 @@
         mSplitSelectDataHolder.setSecondTask(pendingIntent);
     }
 
+    public void setSecondWidget(PendingIntent pendingIntent, Intent widgetIntent) {
+        mSplitSelectDataHolder.setSecondWidget(pendingIntent, widgetIntent);
+    }
+
     /**
      * To be called when we want to launch split pairs from Overview. Split can be initiated from
      * either Overview or home, or all apps. Either both taskIds are set, or a pending intent + a
@@ -380,11 +385,13 @@
         ShortcutInfo secondShortcut = launchData.getSecondShortcut();
         PendingIntent firstPI = launchData.getInitialPendingIntent();
         PendingIntent secondPI = launchData.getSecondPendingIntent();
+        Intent widgetIntent = launchData.getWidgetSecondIntent();
         int firstUserId = launchData.getInitialUserId();
         int secondUserId = launchData.getSecondUserId();
         int initialStagePosition = launchData.getInitialStagePosition();
         Bundle optionsBundle = options1.toBundle();
-
+        Bundle extrasBundle = new Bundle(1);
+        extrasBundle.putParcelable(KEY_EXTRA_WIDGET_INTENT, widgetIntent);
         if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
             final RemoteTransition remoteTransition = getShellRemoteTransition(firstTaskId,
                     secondTaskId, callback, "LaunchSplitPair");
@@ -396,7 +403,7 @@
 
                 case SPLIT_TASK_PENDINGINTENT ->
                         mSystemUiProxy.startIntentAndTask(secondPI, secondUserId, optionsBundle,
-                                firstTaskId, null /*options2*/, initialStagePosition, snapPosition,
+                                firstTaskId, extrasBundle, initialStagePosition, snapPosition,
                                 remoteTransition, shellInstanceId);
 
                 case SPLIT_TASK_SHORTCUT ->
@@ -411,9 +418,9 @@
 
                 case SPLIT_PENDINGINTENT_PENDINGINTENT ->
                         mSystemUiProxy.startIntents(firstPI, firstUserId, firstShortcut,
-                                optionsBundle, secondPI, secondUserId, secondShortcut,
-                                null /*options2*/, initialStagePosition, snapPosition,
-                                remoteTransition, shellInstanceId);
+                                optionsBundle, secondPI, secondUserId, secondShortcut, extrasBundle,
+                                initialStagePosition, snapPosition, remoteTransition,
+                                shellInstanceId);
 
                 case SPLIT_SHORTCUT_TASK ->
                         mSystemUiProxy.startShortcutAndTask(firstShortcut, optionsBundle,
diff --git a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
index 9313342..e705285 100644
--- a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
@@ -72,7 +72,8 @@
      * @return {@code true} if we can attempt launch the widget into split, {@code false} otherwise
      *         to allow launcher to handle the click
      */
-    public boolean handleSecondWidgetSelectionForSplit(View view, PendingIntent pendingIntent) {
+    public boolean handleSecondWidgetSelectionForSplit(View view, PendingIntent pendingIntent,
+            Intent remoteResponseIntent) {
         if (shouldIgnoreSecondSplitLaunch()) {
             return false;
         }
@@ -86,7 +87,7 @@
             Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
 
             view.post(() -> {
-                mController.setSecondTask(pendingIntent);
+                mController.setSecondWidget(pendingIntent, remoteResponseIntent);
                 // Convert original widgetView into bitmap to use for animation
                 Canvas canvas = new Canvas(bitmap);
                 view.draw(canvas);
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 7d15f7b..5443ff9 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -70,6 +70,7 @@
 import com.android.launcher3.celllayout.DelegatedCellDrawing;
 import com.android.launcher3.celllayout.ItemConfiguration;
 import com.android.launcher3.celllayout.ReorderAlgorithm;
+import com.android.launcher3.celllayout.ReorderParameters;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.folder.PreviewBackground;
@@ -1748,8 +1749,11 @@
     protected ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX,
             int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX,
             ItemConfiguration solution) {
-        return createReorderAlgorithm().findReorderSolution(pixelX, pixelY, minSpanX, minSpanY,
-                spanX, spanY, direction, dragView, decX, solution);
+        ItemConfiguration configuration = new ItemConfiguration();
+        copyCurrentStateToSolution(configuration);
+        ReorderParameters parameters = new ReorderParameters(pixelX, pixelY, spanX, spanY, minSpanX,
+                minSpanY, dragView, configuration);
+        return createReorderAlgorithm().findReorderSolution(parameters, decX);
     }
 
     public void copyCurrentStateToSolution(ItemConfiguration solution) {
@@ -1779,8 +1783,12 @@
      */
     public ItemConfiguration calculateReorder(int pixelX, int pixelY, int minSpanX, int minSpanY,
             int spanX, int spanY, View dragView) {
-        return createReorderAlgorithm().calculateReorder(pixelX, pixelY, minSpanX, minSpanY,
-                spanX, spanY, dragView);
+        ItemConfiguration configuration = new ItemConfiguration();
+        copyCurrentStateToSolution(configuration);
+        return createReorderAlgorithm().calculateReorder(
+                new ReorderParameters(pixelX, pixelY, spanX, spanY,  minSpanX, minSpanY, dragView,
+                        configuration)
+        );
     }
 
     int[] performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
diff --git a/src/com/android/launcher3/celllayout/MulticellReorderAlgorithm.java b/src/com/android/launcher3/celllayout/MulticellReorderAlgorithm.java
index 7deb653..8d0cf13 100644
--- a/src/com/android/launcher3/celllayout/MulticellReorderAlgorithm.java
+++ b/src/com/android/launcher3/celllayout/MulticellReorderAlgorithm.java
@@ -48,28 +48,24 @@
     }
 
     @Override
-    public ItemConfiguration closestEmptySpaceReorder(int pixelX, int pixelY, int minSpanX,
-            int minSpanY, int spanX, int spanY) {
+    public ItemConfiguration closestEmptySpaceReorder(ReorderParameters reorderParameters) {
         return removeSeamFromSolution(simulateSeam(
-                () -> super.closestEmptySpaceReorder(pixelX, pixelY, minSpanX, minSpanY, spanX,
-                        spanY)));
+                () -> super.closestEmptySpaceReorder(reorderParameters))
+        );
     }
 
     @Override
-    public ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX,
-            int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX,
-            ItemConfiguration solution) {
+    public ItemConfiguration findReorderSolution(ReorderParameters reorderParameters,
+            boolean decX) {
         return removeSeamFromSolution(simulateSeam(
-                () -> super.findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY,
-                        direction, dragView, decX, solution)));
+                () -> super.findReorderSolution(reorderParameters, decX)));
     }
 
     @Override
-    public ItemConfiguration dropInPlaceSolution(int pixelX, int pixelY, int spanX,
-            int spanY,
-            View dragView) {
-        return removeSeamFromSolution(simulateSeam(
-                () -> super.dropInPlaceSolution(pixelX, pixelY, spanX, spanY, dragView)));
+    public ItemConfiguration dropInPlaceSolution(ReorderParameters reorderParameters) {
+        return removeSeamFromSolution(
+                simulateSeam(() -> super.dropInPlaceSolution(reorderParameters))
+        );
     }
 
     void addSeam() {
diff --git a/src/com/android/launcher3/celllayout/ReorderAlgorithm.java b/src/com/android/launcher3/celllayout/ReorderAlgorithm.java
index 7385c0a..42b6991 100644
--- a/src/com/android/launcher3/celllayout/ReorderAlgorithm.java
+++ b/src/com/android/launcher3/celllayout/ReorderAlgorithm.java
@@ -45,36 +45,28 @@
      * This method differs from closestEmptySpaceReorder and dropInPlaceSolution because this method
      * will move items around and will change the shape of the item if possible to try to find a
      * solution.
-     *
+     * <p>
      * When changing the size of the widget this method will try first subtracting -1 in the x
      * dimension and then subtracting -1 in the y dimension until finding a possible solution or
      * until it no longer can reduce the span.
      *
-     * @param pixelX    X coordinate in pixels in the screen
-     * @param pixelY    Y coordinate in pixels in the screen
-     * @param minSpanX  minimum possible horizontal span it will try to find a solution for.
-     * @param minSpanY  minimum possible vertical span it will try to find a solution for.
-     * @param spanX     horizontal cell span
-     * @param spanY     vertical cell span
-     * @param direction direction in which it will try to push the items intersecting the desired
-     *                  view
-     * @param dragView  view being dragged in reorder
-     * @param decX      whether it will decrease the horizontal or vertical span if it can't find a
-     *                  solution for the current span.
-     * @param solution  variable to store the solution
+     * @param decX     whether it will decrease the horizontal or vertical span if it can't find a
+     *                 solution for the current span.
      * @return the same solution variable
      */
-    public ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX,
-            int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX,
-            ItemConfiguration solution) {
-        return findReorderSolutionRecursive(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY,
-                direction, dragView, decX, solution);
+    public ItemConfiguration findReorderSolution(ReorderParameters reorderParameters,
+            boolean decX) {
+        return findReorderSolutionRecursive(reorderParameters.getPixelX(),
+                reorderParameters.getPixelY(), reorderParameters.getMinSpanX(),
+                reorderParameters.getMinSpanY(), reorderParameters.getSpanX(),
+                reorderParameters.getSpanY(), mCellLayout.mDirectionVector,
+                reorderParameters.getDragView(), decX, reorderParameters.getSolution());
     }
 
 
-    private ItemConfiguration findReorderSolutionRecursive(int pixelX, int pixelY,
-            int minSpanX, int minSpanY, int spanX, int spanY, int[] direction, View dragView,
-            boolean decX, ItemConfiguration solution) {
+    private ItemConfiguration findReorderSolutionRecursive(int pixelX, int pixelY, int minSpanX,
+            int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX,
+            ItemConfiguration solution) {
         // Copy the current state into the solution. This solution will be manipulated as necessary.
         mCellLayout.copyCurrentStateToSolution(solution);
         // Copy the current occupied array into the temporary occupied array. This array will be
@@ -89,8 +81,8 @@
         boolean success;
         // First we try the exact nearest position of the item being dragged,
         // we will then want to try to move this around to other neighbouring positions
-        success = rearrangementExists(result[0], result[1], spanX, spanY, direction,
-                dragView, solution);
+        success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
+                solution);
 
         if (!success) {
             // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
@@ -135,10 +127,11 @@
         // and not by the views hash which is "random".
         // The views are sorted twice, once for the X position and a second time for the Y position
         // to ensure same order everytime.
-        Comparator comparator = Comparator.comparing(view ->
-                        ((CellLayoutLayoutParams) ((View) view).getLayoutParams()).getCellX())
-                .thenComparing(view ->
-                        ((CellLayoutLayoutParams) ((View) view).getLayoutParams()).getCellY());
+        Comparator comparator = Comparator.comparing(
+                view -> ((CellLayoutLayoutParams) ((View) view).getLayoutParams()).getCellX()
+        ).thenComparing(
+                view -> ((CellLayoutLayoutParams) ((View) view).getLayoutParams()).getCellY()
+        );
         List<View> views = solution.map.keySet().stream().sorted(comparator).toList();
         for (View child : views) {
             if (child == ignoreView) continue;
@@ -158,15 +151,13 @@
         // First we try to find a solution which respects the push mechanic. That is,
         // we try to find a solution such that no displaced item travels through another item
         // without also displacing that item.
-        if (attemptPushInDirection(intersectingViews, occupiedRect, direction,
-                ignoreView,
+        if (attemptPushInDirection(intersectingViews, occupiedRect, direction, ignoreView,
                 solution)) {
             return true;
         }
 
         // Next we try moving the views as a block, but without requiring the push mechanic.
-        if (addViewsToTempLocation(intersectingViews, occupiedRect, direction,
-                ignoreView,
+        if (addViewsToTempLocation(intersectingViews, occupiedRect, direction, ignoreView,
                 solution)) {
             return true;
         }
@@ -180,8 +171,8 @@
         return true;
     }
 
-    private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
-            int[] direction, ItemConfiguration currentState) {
+    private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop, int[] direction,
+            ItemConfiguration currentState) {
         CellAndSpan c = currentState.map.get(v);
         boolean success = false;
         mCellLayout.mTmpOccupied.markCells(c, false);
@@ -305,16 +296,16 @@
             int temp = direction[1];
             direction[1] = 0;
 
-            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
-                    ignoreView, solution)) {
+            if (pushViewsToTempLocation(intersectingViews, occupied, direction, ignoreView,
+                    solution)) {
                 return true;
             }
             direction[1] = temp;
             temp = direction[0];
             direction[0] = 0;
 
-            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
-                    ignoreView, solution)) {
+            if (pushViewsToTempLocation(intersectingViews, occupied, direction, ignoreView,
+                    solution)) {
                 return true;
             }
             // Revert the direction
@@ -325,16 +316,16 @@
             direction[1] *= -1;
             temp = direction[1];
             direction[1] = 0;
-            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
-                    ignoreView, solution)) {
+            if (pushViewsToTempLocation(intersectingViews, occupied, direction, ignoreView,
+                    solution)) {
                 return true;
             }
 
             direction[1] = temp;
             temp = direction[0];
             direction[0] = 0;
-            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
-                    ignoreView, solution)) {
+            if (pushViewsToTempLocation(intersectingViews, occupied, direction, ignoreView,
+                    solution)) {
                 return true;
             }
             // revert the direction
@@ -345,15 +336,15 @@
         } else {
             // If the direction vector has a single non-zero component, we push first in the
             // direction of the vector
-            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
-                    ignoreView, solution)) {
+            if (pushViewsToTempLocation(intersectingViews, occupied, direction, ignoreView,
+                    solution)) {
                 return true;
             }
             // Then we try the opposite direction
             direction[0] *= -1;
             direction[1] *= -1;
-            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
-                    ignoreView, solution)) {
+            if (pushViewsToTempLocation(intersectingViews, occupied, direction, ignoreView,
+                    solution)) {
                 return true;
             }
             // Switch the direction back
@@ -367,16 +358,16 @@
             int temp = direction[1];
             direction[1] = direction[0];
             direction[0] = temp;
-            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
-                    ignoreView, solution)) {
+            if (pushViewsToTempLocation(intersectingViews, occupied, direction, ignoreView,
+                    solution)) {
                 return true;
             }
 
             // Then we try the opposite direction
             direction[0] *= -1;
             direction[1] *= -1;
-            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
-                    ignoreView, solution)) {
+            if (pushViewsToTempLocation(intersectingViews, occupied, direction, ignoreView,
+                    solution)) {
                 return true;
             }
             // Switch the direction back
@@ -446,63 +437,59 @@
     /**
      * Returns a "reorder" if there is empty space without rearranging anything.
      *
-     * @param pixelX   X coordinate in pixels in the screen
-     * @param pixelY   Y coordinate in pixels in the screen
-     * @param spanX    horizontal cell span
-     * @param spanY    vertical cell span
-     * @param dragView view being dragged in reorder
      * @return the configuration that represents the found reorder
      */
-    public ItemConfiguration dropInPlaceSolution(int pixelX, int pixelY, int spanX,
-            int spanY, View dragView) {
-        int[] result = mCellLayout.findNearestAreaIgnoreOccupied(pixelX, pixelY, spanX, spanY,
-                new int[2]);
+    public ItemConfiguration dropInPlaceSolution(ReorderParameters reorderParameters) {
+        int[] result = mCellLayout.findNearestAreaIgnoreOccupied(reorderParameters.getPixelX(),
+                reorderParameters.getPixelY(), reorderParameters.getSpanX(),
+                reorderParameters.getSpanY(), new int[2]);
         ItemConfiguration solution = new ItemConfiguration();
         mCellLayout.copyCurrentStateToSolution(solution);
 
         solution.isSolution = !isConfigurationRegionOccupied(
-                new Rect(result[0], result[1], result[0] + spanX, result[1] + spanY),
-                solution,
-                dragView
-        );
+                new Rect(result[0], result[1], result[0] + reorderParameters.getSpanX(),
+                        result[1] + reorderParameters.getSpanY()), solution,
+                reorderParameters.getDragView());
         if (!solution.isSolution) {
             return solution;
         }
         solution.cellX = result[0];
         solution.cellY = result[1];
-        solution.spanX = spanX;
-        solution.spanY = spanY;
+        solution.spanX = reorderParameters.getSpanX();
+        solution.spanY = reorderParameters.getSpanY();
         return solution;
     }
 
-    private boolean isConfigurationRegionOccupied(Rect region,
-            ItemConfiguration configuration, View ignoreView) {
-        return configuration.map.entrySet()
+    private boolean isConfigurationRegionOccupied(Rect region, ItemConfiguration configuration,
+            View ignoreView) {
+        return configuration.map
+                .entrySet()
                 .stream()
                 .filter(entry -> entry.getKey() != ignoreView)
                 .map(Entry::getValue)
-                .anyMatch(cellAndSpan -> region.intersect(cellAndSpan.cellX, cellAndSpan.cellY,
+                .anyMatch(cellAndSpan -> region.intersect(
+                        cellAndSpan.cellX,
+                        cellAndSpan.cellY,
                         cellAndSpan.cellX + cellAndSpan.spanX,
-                        cellAndSpan.cellY + cellAndSpan.spanY));
+                        cellAndSpan.cellY + cellAndSpan.spanY
+                        )
+                );
     }
 
     /**
      * Returns a "reorder" where we simply drop the item in the closest empty space, without moving
      * any other item in the way.
      *
-     * @param pixelX X coordinate in pixels in the screen
-     * @param pixelY Y coordinate in pixels in the screen
-     * @param spanX  horizontal cell span
-     * @param spanY  vertical cell span
      * @return the configuration that represents the found reorder
      */
-    public ItemConfiguration closestEmptySpaceReorder(int pixelX, int pixelY,
-            int minSpanX, int minSpanY, int spanX, int spanY) {
+    public ItemConfiguration closestEmptySpaceReorder(ReorderParameters reorderParameters) {
         ItemConfiguration solution = new ItemConfiguration();
         int[] result = new int[2];
         int[] resultSpan = new int[2];
-        mCellLayout.findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, result,
-                resultSpan);
+        mCellLayout.findNearestVacantArea(reorderParameters.getPixelX(),
+                reorderParameters.getPixelY(), reorderParameters.getMinSpanX(),
+                reorderParameters.getMinSpanY(), reorderParameters.getSpanX(),
+                reorderParameters.getSpanY(), result, resultSpan);
         if (result[0] >= 0 && result[1] >= 0) {
             mCellLayout.copyCurrentStateToSolution(solution);
             solution.cellX = result[0];
@@ -521,32 +508,19 @@
      * the workspace to make space for the new item, this function return a solution for that
      * reorder.
      *
-     * @param pixelX   X coordinate in the screen of the dragView in pixels
-     * @param pixelY   Y coordinate in the screen of the dragView in pixels
-     * @param minSpanX minimum horizontal span the item can be shrunk to
-     * @param minSpanY minimum vertical span the item can be shrunk to
-     * @param spanX    occupied horizontal span
-     * @param spanY    occupied vertical span
-     * @param dragView the view of the item being draged
      * @return returns a solution for the given parameters, the solution contains all the icons and
      * the locations they should be in the given solution.
      */
-    public ItemConfiguration calculateReorder(int pixelX, int pixelY, int minSpanX,
-            int minSpanY, int spanX, int spanY, View dragView) {
-        getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView,
-                mCellLayout.mDirectionVector);
+    public ItemConfiguration calculateReorder(ReorderParameters reorderParameters) {
+        getDirectionVectorForDrop(reorderParameters, mCellLayout.mDirectionVector);
 
-        ItemConfiguration dropInPlaceSolution = dropInPlaceSolution(pixelX, pixelY, spanX, spanY,
-                dragView);
+        ItemConfiguration dropInPlaceSolution = dropInPlaceSolution(reorderParameters);
 
         // Find a solution involving pushing / displacing any items in the way
-        ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX,
-                minSpanY, spanX, spanY, mCellLayout.mDirectionVector, dragView, true,
-                new ItemConfiguration());
+        ItemConfiguration swapSolution = findReorderSolution(reorderParameters, true);
 
         // We attempt the approach which doesn't shuffle views at all
-        ItemConfiguration closestSpaceSolution = closestEmptySpaceReorder(pixelX, pixelY, minSpanX,
-                minSpanY, spanX, spanY);
+        ItemConfiguration closestSpaceSolution = closestEmptySpaceReorder(reorderParameters);
 
         // If the reorder solution requires resizing (shrinking) the item being dropped, we instead
         // favor a solution in which the item is not resized, but
@@ -586,21 +560,26 @@
      * those cells. Instead we use some heuristics to often lock the vector to up, down, left
      * or right, which helps make pushing feel right.
      */
-    private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
-            int spanY, View dragView, int[] resultDirection) {
+    public void getDirectionVectorForDrop(ReorderParameters reorderParameters,
+            int[] resultDirection) {
 
         //TODO(adamcohen) b/151776141 use the items visual center for the direction vector
         int[] targetDestination = new int[2];
 
-        mCellLayout.findNearestAreaIgnoreOccupied(dragViewCenterX, dragViewCenterY, spanX, spanY,
-                targetDestination);
+        mCellLayout.findNearestAreaIgnoreOccupied(reorderParameters.getPixelX(),
+                reorderParameters.getPixelY(), reorderParameters.getSpanX(),
+                reorderParameters.getSpanY(), targetDestination);
         Rect dragRect = new Rect();
-        mCellLayout.cellToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
-        dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
+        mCellLayout.cellToRect(targetDestination[0], targetDestination[1],
+                reorderParameters.getSpanX(), reorderParameters.getSpanY(), dragRect);
+        dragRect.offset(reorderParameters.getPixelX() - dragRect.centerX(),
+                reorderParameters.getPixelY() - dragRect.centerY());
 
         Rect region = new Rect(targetDestination[0], targetDestination[1],
-                targetDestination[0] + spanX, targetDestination[1] + spanY);
-        Rect dropRegionRect = mCellLayout.getIntersectingRectanglesInRegion(region, dragView);
+                targetDestination[0] + reorderParameters.getSpanX(),
+                targetDestination[1] + reorderParameters.getSpanY());
+        Rect dropRegionRect = mCellLayout.getIntersectingRectanglesInRegion(region,
+                reorderParameters.getDragView());
         if (dropRegionRect == null) dropRegionRect = new Rect(region);
 
         int dropRegionSpanX = dropRegionRect.width();
@@ -609,13 +588,17 @@
         mCellLayout.cellToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
                 dropRegionRect.height(), dropRegionRect);
 
-        int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
-        int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
+        int deltaX = (dropRegionRect.centerX() - reorderParameters.getPixelX())
+                / reorderParameters.getSpanX();
+        int deltaY = (dropRegionRect.centerY() - reorderParameters.getPixelY())
+                / reorderParameters.getSpanY();
 
-        if (dropRegionSpanX == mCellLayout.getCountX() || spanX == mCellLayout.getCountX()) {
+        if (dropRegionSpanX == mCellLayout.getCountX()
+                || reorderParameters.getSpanX() == mCellLayout.getCountX()) {
             deltaX = 0;
         }
-        if (dropRegionSpanY == mCellLayout.getCountY() || spanY == mCellLayout.getCountY()) {
+        if (dropRegionSpanY == mCellLayout.getCountY()
+                || reorderParameters.getSpanY() == mCellLayout.getCountY()) {
             deltaY = 0;
         }
 
diff --git a/src/com/android/launcher3/celllayout/ReorderParameters.kt b/src/com/android/launcher3/celllayout/ReorderParameters.kt
new file mode 100644
index 0000000..3fdf35c
--- /dev/null
+++ b/src/com/android/launcher3/celllayout/ReorderParameters.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 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.launcher3.celllayout
+
+import android.view.View
+
+class ReorderParameters(
+    val pixelX: Int,
+    val pixelY: Int,
+    val spanX: Int,
+    val spanY: Int,
+    val minSpanX: Int,
+    val minSpanY: Int,
+    val dragView: View?,
+    val solution: ItemConfiguration
+) {}
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index 40bb6ad..1b1d347 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -15,8 +15,8 @@
  */
 package com.android.launcher3.testing;
 
-import static com.android.launcher3.Flags.enableGridOnlyOverview;
 import static com.android.launcher3.allapps.AllAppsStore.DEFER_UPDATES_TEST;
+import static com.android.launcher3.Flags.enableGridOnlyOverview;
 import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
@@ -297,10 +297,8 @@
     }
 
     protected boolean isLauncherInitialized() {
-        Launcher launcher = Launcher.ACTIVITY_TRACKER.getCreatedActivity();
-        return launcher == null
-                || (LauncherAppState.getInstance(mContext).getModel().isModelLoaded()
-                && !launcher.isBindingItems());
+        return Launcher.ACTIVITY_TRACKER.getCreatedActivity() == null
+                || LauncherAppState.getInstance(mContext).getModel().isModelLoaded();
     }
 
     protected Activity getCurrentActivity() {
diff --git a/src/com/android/launcher3/util/VibratorWrapper.java b/src/com/android/launcher3/util/VibratorWrapper.java
index e32fec2..f283fb6 100644
--- a/src/com/android/launcher3/util/VibratorWrapper.java
+++ b/src/com/android/launcher3/util/VibratorWrapper.java
@@ -240,7 +240,7 @@
 
     /** Indicates that search has been invoked. */
     public void vibrateForSearch() {
-        if (mSearchEffect != null && FeatureFlags.ENABLE_SEARCH_HAPTIC_COMMIT.get()) {
+        if (mSearchEffect != null) {
             vibrate(mSearchEffect);
         }
     }
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index 5ec1022..9855b20 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -105,6 +105,7 @@
             mNavBarScrimPaint.setColor(navBarScrimColor);
             invalidate();
         }
+        setupNavBarColor();
     }
 
     @Override
@@ -218,10 +219,18 @@
     }
 
     protected void setupNavBarColor() {
-        boolean isSheetDark = Themes.getAttrBoolean(getContext(), R.attr.isMainColorDark);
-        getSystemUiController().updateUiState(
-                SystemUiController.UI_STATE_WIDGET_BOTTOM_SHEET,
-                isSheetDark ? SystemUiController.FLAG_DARK_NAV : SystemUiController.FLAG_LIGHT_NAV);
+        boolean isNavBarDark = Themes.getAttrBoolean(getContext(), R.attr.isMainColorDark);
+
+        // In light mode, landscape reverses navbar background color.
+        boolean isPhoneLandscape =
+                !mActivityContext.getDeviceProfile().isTablet && mInsets.bottom == 0;
+        if (!isNavBarDark && isPhoneLandscape) {
+            isNavBarDark = true;
+        }
+
+        getSystemUiController().updateUiState(SystemUiController.UI_STATE_WIDGET_BOTTOM_SHEET,
+                isNavBarDark ? SystemUiController.FLAG_DARK_NAV
+                        : SystemUiController.FLAG_LIGHT_NAV);
     }
 
     protected SystemUiController getSystemUiController() {
diff --git a/tests/assets/ReorderWidgets/full_reorder_case b/tests/assets/ReorderWidgets/full_reorder_case
index 33ebaae..850e4fd 100644
--- a/tests/assets/ReorderWidgets/full_reorder_case
+++ b/tests/assets/ReorderWidgets/full_reorder_case
@@ -20,7 +20,7 @@
 bbmm
 iimm
 iiaa
-arguments: 0 3
+arguments: 0 2
 board: 4x4
 xxxx
 bbii
diff --git a/tests/assets/ReorderWidgets/simple_reorder_case b/tests/assets/ReorderWidgets/simple_reorder_case
index f5eb7b6..2c50ce4 100644
--- a/tests/assets/ReorderWidgets/simple_reorder_case
+++ b/tests/assets/ReorderWidgets/simple_reorder_case
@@ -34,7 +34,7 @@
 --mm
 --mm
 ----
-arguments: 3 3
+arguments: 2 2
 board: 4x4
 xxxx
 ----
diff --git a/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java b/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
index 28471f6..e1af774 100644
--- a/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
+++ b/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
@@ -191,9 +191,21 @@
 
         int[] testCaseXYinPixels = new int[2];
         cl.regionToCenterPoint(x, y, spanX, spanY, testCaseXYinPixels);
-        ItemConfiguration solution = cl.createReorderAlgorithm().calculateReorder(
-                testCaseXYinPixels[0], testCaseXYinPixels[1], minSpanX, minSpanY, spanX, spanY,
-                null);
+        ItemConfiguration configuration = new ItemConfiguration();
+        cl.copyCurrentStateToSolution(configuration);
+        ItemConfiguration solution = cl.createReorderAlgorithm()
+                .calculateReorder(
+                        new ReorderParameters(
+                                testCaseXYinPixels[0],
+                                testCaseXYinPixels[1],
+                                spanX,
+                                spanY,
+                                minSpanX,
+                                minSpanY,
+                                null,
+                                configuration
+                        )
+                );
         if (solution == null) {
             solution = new ItemConfiguration();
             solution.isSolution = false;
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java b/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java
index 38cc321..4a0131b 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java
@@ -20,6 +20,8 @@
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.UiObject2;
 
+import com.android.launcher3.testing.shared.TestProtocol;
+
 /** Represents the menu of an overview task. */
 public class OverviewTaskMenu {
 
@@ -59,8 +61,13 @@
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                      "before tapping the app info menu item")) {
-            mLauncher.clickLauncherObject(
-                    mLauncher.findObjectInContainer(mMenu, By.text("App info")));
+            mLauncher.executeAndWaitForLauncherEvent(
+                    () -> mLauncher.clickLauncherObject(
+                            mLauncher.findObjectInContainer(mMenu, By.text("App info"))),
+                    event -> TestProtocol.LAUNCHER_ACTIVITY_STOPPED_MESSAGE
+                            .equals(event.getClassName().toString()),
+                    () -> "Launcher activity didn't stop",
+                    "tapped app info menu item");
 
             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
                     "tapped app info menu item")) {