Merge "Launcher side implementation of gesture seekable back to home animation." into tm-dev
diff --git a/Android.bp b/Android.bp
index c980a2e..3aa9394 100644
--- a/Android.bp
+++ b/Android.bp
@@ -31,7 +31,6 @@
         "androidx.test.uiautomator_uiautomator",
         "androidx.preference_preference",
         "SystemUISharedLib",
-        "SystemUIAnimationLib",
     ],
     srcs: [
         "tests/tapl/**/*.java",
@@ -197,7 +196,6 @@
         "lottie",
         "SystemUISharedLib",
         "SystemUI-statsd",
-        "SystemUIAnimationLib",
     ],
     manifest: "quickstep/AndroidManifest.xml",
     min_sdk_version: "current",
@@ -210,6 +208,8 @@
     srcs: [
         "ext_tests/src/**/*.java",
         "ext_tests/src/**/*.kt",
+        "quickstep/ext_tests/src/**/*.java",
+        "quickstep/ext_tests/src/**/*.kt",
     ],
 }
 
@@ -289,7 +289,6 @@
         "SystemUISharedLib",
         "Launcher3CommonDepsLib",
         "QuickstepResLib",
-        "SystemUIAnimationLib",
     ],
     manifest: "quickstep/AndroidManifest.xml",
     platform_apis: true,
diff --git a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
index 02206c0..d16e12c 100644
--- a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
+++ b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
@@ -29,8 +29,10 @@
 import androidx.annotation.Keep;
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.ShortcutAndWidgetContainer;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -205,6 +207,32 @@
                 }
             }
 
+            case TestProtocol.REQUEST_USE_TEST_WORKSPACE_LAYOUT: {
+                useTestWorkspaceLayout(true);
+                return response;
+            }
+
+            case TestProtocol.REQUEST_USE_DEFAULT_WORKSPACE_LAYOUT: {
+                useTestWorkspaceLayout(false);
+                return response;
+            }
+
+            case TestProtocol.REQUEST_HOTSEAT_ICON_NAMES: {
+                return getLauncherUIProperty(Bundle::putStringArrayList, l -> {
+                    ShortcutAndWidgetContainer hotseatIconsContainer =
+                            l.getHotseat().getShortcutsAndWidgets();
+                    ArrayList<String> hotseatIconNames = new ArrayList<>();
+
+                    for (int i = 0; i < hotseatIconsContainer.getChildCount(); i++) {
+                        // Use unchecked cast to catch changes in hotseat layout
+                        BubbleTextView icon = (BubbleTextView) hotseatIconsContainer.getChildAt(i);
+                        hotseatIconNames.add((String) icon.getText());
+                    }
+
+                    return hotseatIconNames;
+                });
+            }
+
             case TestProtocol.REQUEST_GET_ACTIVITIES_CREATED_COUNT: {
                 response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, sActivitiesCreatedCount);
                 return response;
@@ -223,4 +251,15 @@
                 return super.call(method, arg, extras);
         }
     }
+
+    private void useTestWorkspaceLayout(boolean useTestWorkspaceLayout) {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            LauncherSettings.Settings.call(mContext.getContentResolver(), useTestWorkspaceLayout
+                    ? LauncherSettings.Settings.METHOD_SET_USE_TEST_WORKSPACE_LAYOUT_FLAG
+                    : LauncherSettings.Settings.METHOD_CLEAR_USE_TEST_WORKSPACE_LAYOUT_FLAG);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
 }
diff --git a/quickstep/ext_tests/src/com/android/quickstep/DebugQuickstepTestInformationHandler.java b/quickstep/ext_tests/src/com/android/quickstep/DebugQuickstepTestInformationHandler.java
new file mode 100644
index 0000000..e5f0295
--- /dev/null
+++ b/quickstep/ext_tests/src/com/android/quickstep/DebugQuickstepTestInformationHandler.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2022 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 com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.taskbar.LauncherTaskbarUIController;
+import com.android.launcher3.testing.DebugTestInformationHandler;
+import com.android.launcher3.testing.TestProtocol;
+
+import java.util.concurrent.ExecutionException;
+
+/**
+ * Class to handle requests from tests, including debug ones, to Quickstep Launcher builds.
+ */
+public abstract class DebugQuickstepTestInformationHandler extends QuickstepTestInformationHandler {
+
+    private final DebugTestInformationHandler mDebugTestInformationHandler;
+
+    public DebugQuickstepTestInformationHandler(Context context) {
+        super(context);
+        mDebugTestInformationHandler = new DebugTestInformationHandler(context);
+    }
+
+    @Override
+    public Bundle call(String method, String arg, @Nullable Bundle extras) {
+        Bundle response = new Bundle();
+        switch (method) {
+            case TestProtocol.REQUEST_ENABLE_MANUAL_TASKBAR_STASHING:
+                runOnUIThread(l -> {
+                    enableManualTaskbarStashing(l, true);
+                });
+                return response;
+
+            case TestProtocol.REQUEST_DISABLE_MANUAL_TASKBAR_STASHING:
+                runOnUIThread(l -> {
+                    enableManualTaskbarStashing(l, false);
+                });
+                return response;
+
+            case TestProtocol.REQUEST_UNSTASH_TASKBAR_IF_STASHED:
+                runOnUIThread(l -> {
+                    enableManualTaskbarStashing(l, true);
+
+                    BaseQuickstepLauncher quickstepLauncher = (BaseQuickstepLauncher) l;
+                    LauncherTaskbarUIController taskbarUIController =
+                            quickstepLauncher.getTaskbarUIController();
+
+                    // Allow null-pointer to catch illegal states.
+                    taskbarUIController.unstashTaskbarIfStashed();
+
+                    enableManualTaskbarStashing(l, false);
+                });
+                return response;
+
+            case TestProtocol.REQUEST_STASHED_TASKBAR_HEIGHT: {
+                final Resources resources = mContext.getResources();
+                response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
+                        resources.getDimensionPixelSize(R.dimen.taskbar_stashed_size));
+                return response;
+            }
+
+            default:
+                response = super.call(method, arg, extras);
+                if (response != null) return response;
+                return mDebugTestInformationHandler.call(method, arg, extras);
+        }
+    }
+
+    private void enableManualTaskbarStashing(Launcher launcher, boolean enable) {
+        BaseQuickstepLauncher quickstepLauncher = (BaseQuickstepLauncher) launcher;
+        LauncherTaskbarUIController taskbarUIController =
+                quickstepLauncher.getTaskbarUIController();
+
+        // Allow null-pointer to catch illegal states.
+        taskbarUIController.enableManualStashingForTests(enable);
+    }
+
+    /**
+     * Runs the given command on the UI thread.
+     */
+    private static void runOnUIThread(UIThreadCommand command) {
+        try {
+            MAIN_EXECUTOR.submit(() -> {
+                command.execute(Launcher.ACTIVITY_TRACKER.getCreatedActivity());
+                return null;
+            }).get();
+        } catch (ExecutionException | InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private interface UIThreadCommand {
+
+        void execute(Launcher launcher);
+    }
+}
+
diff --git a/quickstep/res/drawable-v28/gesture_tutorial_action_button_background.xml b/quickstep/res/drawable-v28/gesture_tutorial_action_button_background.xml
index 57423c2..710482f 100644
--- a/quickstep/res/drawable-v28/gesture_tutorial_action_button_background.xml
+++ b/quickstep/res/drawable-v28/gesture_tutorial_action_button_background.xml
@@ -16,5 +16,5 @@
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
     android:shape="rectangle">
     <corners android:radius="?android:attr/dialogCornerRadius"/>
-    <solid android:color="@color/gesture_tutorial_primary_color"/>
+    <solid android:color="?android:attr/colorAccent"/>
 </shape>
\ No newline at end of file
diff --git a/quickstep/res/drawable/gesture_tutorial_action_button_background.xml b/quickstep/res/drawable/gesture_tutorial_action_button_background.xml
index ac6a52a..98dc1a5 100644
--- a/quickstep/res/drawable/gesture_tutorial_action_button_background.xml
+++ b/quickstep/res/drawable/gesture_tutorial_action_button_background.xml
@@ -25,7 +25,7 @@
         <shape
             android:shape="rectangle">
             <corners android:radius="50dp"/>
-            <solid android:color="@color/gesture_tutorial_primary_color"/>
+            <solid android:color="?android:attr/colorAccent"/>
         </shape>
     </item>
 </layer-list>
\ No newline at end of file
diff --git a/quickstep/res/drawable/gesture_tutorial_cancel_button_background.xml b/quickstep/res/drawable/gesture_tutorial_cancel_button_background.xml
index 0a34af6..7762615 100644
--- a/quickstep/res/drawable/gesture_tutorial_cancel_button_background.xml
+++ b/quickstep/res/drawable/gesture_tutorial_cancel_button_background.xml
@@ -17,5 +17,5 @@
     android:shape="rectangle">
     <corners android:radius="50dp"/>
     <solid android:color="@android:color/transparent"/>
-    <stroke android:width="1dp" android:color="@color/gesture_tutorial_primary_color"/>
+    <stroke android:width="1dp" android:color="?android:attr/colorAccent"/>
 </shape>
\ No newline at end of file
diff --git a/quickstep/res/drawable/gesture_tutorial_finger_dot.xml b/quickstep/res/drawable/gesture_tutorial_finger_dot.xml
index 5f8aafd..cbb2612 100644
--- a/quickstep/res/drawable/gesture_tutorial_finger_dot.xml
+++ b/quickstep/res/drawable/gesture_tutorial_finger_dot.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
     android:shape="oval">
-    <solid android:color="@color/gesture_tutorial_primary_color" />
+    <solid android:color="?android:attr/colorAccent" />
     <size android:width="92dp" android:height="92dp"/>
 </shape>
\ No newline at end of file
diff --git a/quickstep/res/drawable/gesture_tutorial_loop_back.xml b/quickstep/res/drawable/gesture_tutorial_loop_back.xml
index d2909ff..ae47709 100644
--- a/quickstep/res/drawable/gesture_tutorial_loop_back.xml
+++ b/quickstep/res/drawable/gesture_tutorial_loop_back.xml
@@ -85,7 +85,7 @@
                     <path
                         android:name="_R_G_L_0_G_D_0_P_0"
                         android:fillAlpha="0.25"
-                        android:fillColor="@color/gesture_tutorial_primary_color"
+                        android:fillColor="?android:attr/colorAccent"
                         android:fillType="nonZero"
                         android:pathData=" M12.5 -446 C12.5,-446 12.5,446 12.5,446 C12.5,446 -12.5,446 -12.5,446 C-12.5,446 -12.5,-446 -12.5,-446 C-12.5,-446 12.5,-446 12.5,-446c " />
                 </group>
diff --git a/quickstep/res/drawable/gesture_tutorial_loop_home.xml b/quickstep/res/drawable/gesture_tutorial_loop_home.xml
index 931f8c0..bed35dd 100644
--- a/quickstep/res/drawable/gesture_tutorial_loop_home.xml
+++ b/quickstep/res/drawable/gesture_tutorial_loop_home.xml
@@ -81,7 +81,7 @@
                     <path
                         android:name="_R_G_L_1_G_D_0_P_0"
                         android:fillAlpha="0.25"
-                        android:fillColor="@color/gesture_tutorial_primary_color"
+                        android:fillColor="?android:attr/colorAccent"
                         android:fillType="nonZero"
                         android:pathData=" M206 -12.5 C206,-12.5 206,12.5 206,12.5 C206,12.5 -206,12.5 -206,12.5 C-206,12.5 -206,-12.5 -206,-12.5 C-206,-12.5 206,-12.5 206,-12.5c " />
                 </group>
diff --git a/quickstep/res/drawable/gesture_tutorial_loop_overview.xml b/quickstep/res/drawable/gesture_tutorial_loop_overview.xml
index a4c532b..53e8b5f 100644
--- a/quickstep/res/drawable/gesture_tutorial_loop_overview.xml
+++ b/quickstep/res/drawable/gesture_tutorial_loop_overview.xml
@@ -81,7 +81,7 @@
                     <path
                         android:name="_R_G_L_1_G_D_0_P_0"
                         android:fillAlpha="0.25"
-                        android:fillColor="@color/gesture_tutorial_primary_color"
+                        android:fillColor="?android:attr/colorAccent"
                         android:fillType="nonZero"
                         android:pathData=" M206 -12.5 C206,-12.5 206,12.5 206,12.5 C206,12.5 -206,12.5 -206,12.5 C-206,12.5 -206,-12.5 -206,-12.5 C-206,-12.5 206,-12.5 206,-12.5c " />
                 </group>
diff --git a/quickstep/res/values/colors.xml b/quickstep/res/values/colors.xml
index 671a617..8f439a2 100644
--- a/quickstep/res/values/colors.xml
+++ b/quickstep/res/values/colors.xml
@@ -13,7 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<resources>
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
 
     <color name="chip_hint_foreground_color">#fff</color>
     <color name="chip_scrim_start_color">#39000000</color>
@@ -41,8 +41,6 @@
     <color name="gesture_tutorial_fake_task_view_color">#6DA1FF</color> <!-- Light Blue -->
     <!-- Must contrast gesture_tutorial_fake_wallpaper_color -->
     <color name="gesture_tutorial_fake_previous_task_view_color">#3C4043</color> <!-- Gray -->
-    <color name="gesture_tutorial_action_button_label_color">#FF000000</color>
-    <color name="gesture_tutorial_primary_color">#B7F29F</color> <!-- Light Green -->
     <color name="gesture_tutorial_taskbar_color">#202124</color>
 
     <!-- Mock hotseat -->
diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml
index 6aa4883..7225220 100644
--- a/quickstep/res/values/styles.xml
+++ b/quickstep/res/values/styles.xml
@@ -88,7 +88,8 @@
 
     <style name="TextAppearance.GestureTutorial.Feedback.Subtext"
         parent="TextAppearance.GestureTutorial.Feedback.Subtitle">
-        <item name="android:textColor">@color/gesture_tutorial_primary_color</item>
+        <item name="android:textSize">16sp</item>
+        <item name="android:textColor">?android:attr/colorAccent</item>
         <item name="android:gravity">start</item>
     </style>
 
@@ -100,7 +101,7 @@
     <style name="TextAppearance.GestureTutorial.ButtonLabel"
         parent="TextAppearance.GestureTutorial.CallToAction">
         <item name="android:gravity">center</item>
-        <item name="android:textColor">@color/gesture_tutorial_action_button_label_color</item>
+        <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
         <item name="android:letterSpacing">0.02</item>
         <item name="android:textSize">16sp</item>
         <item name="android:textAllCaps">false</item>
@@ -108,12 +109,12 @@
 
     <style name="TextAppearance.GestureTutorial.CancelButtonLabel"
         parent="TextAppearance.GestureTutorial.ButtonLabel">
-        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:textColor">?android:attr/colorAccent</item>
     </style>
 
     <style name="TextAppearance.GestureTutorial.TextButtonLabel"
         parent="TextAppearance.GestureTutorial.ButtonLabel">
-        <item name="android:textColor">@color/gesture_tutorial_primary_color</item>
+        <item name="android:textColor">?android:attr/colorAccent</item>
     </style>
 
     <style name="TextAppearance.GestureTutorial.LinkText"
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 4d4143a..f14e2a2 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -44,6 +44,7 @@
 import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_TRANSITIONS;
 import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
 import static com.android.launcher3.statehandlers.DepthController.DEPTH;
+import static com.android.launcher3.testing.TestProtocol.BAD_STATE;
 import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
 import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
 import static com.android.launcher3.views.FloatingIconView.getFloatingIconView;
@@ -77,6 +78,7 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.util.Log;
 import android.util.Pair;
 import android.util.Size;
 import android.view.SurfaceControl;
@@ -136,6 +138,7 @@
 import com.android.wm.shell.startingsurface.IStartingWindowListener;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.LinkedHashMap;
 import java.util.List;
 
@@ -615,9 +618,28 @@
         RecentsView overview = mLauncher.getOverviewPanel();
         ObjectAnimator alpha = ObjectAnimator.ofFloat(overview,
                 RecentsView.CONTENT_ALPHA, alphas);
+        Log.d(BAD_STATE, "QTM composeViewContentAnimator alphas=" + Arrays.toString(alphas));
+        alpha.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                Log.d(BAD_STATE, "QTM composeViewContentAnimator onStart");
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                float alpha = overview == null ? -1 : RecentsView.CONTENT_ALPHA.get(overview);
+                Log.d(BAD_STATE, "QTM composeViewContentAnimator onCancel, alpha=" + alpha);
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                Log.d(BAD_STATE, "QTM composeViewContentAnimator onEnd");
+            }
+        });
         alpha.setDuration(CONTENT_ALPHA_DURATION);
         alpha.setInterpolator(LINEAR);
         anim.play(alpha);
+        Log.d(BAD_STATE, "QTM composeViewContentAnimator setFreezeVisibility=true");
         overview.setFreezeViewVisibility(true);
 
         ObjectAnimator scaleAnim = ObjectAnimator.ofFloat(overview, SCALE_PROPERTY, scales);
@@ -626,6 +648,7 @@
         anim.play(scaleAnim);
 
         return () -> {
+            Log.d(BAD_STATE, "QTM composeViewContentAnimator onEnd setFreezeVisibility=false");
             overview.setFreezeViewVisibility(false);
             SCALE_PROPERTY.set(overview, 1f);
             mLauncher.getStateManager().reapplyState();
@@ -1312,8 +1335,8 @@
             }
         }
 
-        return mLauncher.getFirstMatchForAppClose(launchCookieItemId,
-                packageName, UserHandle.of(runningTaskTarget.taskInfo.userId));
+        return mLauncher.getFirstMatchForAppClose(launchCookieItemId, packageName,
+                UserHandle.of(runningTaskTarget.taskInfo.userId), true /* supportsAllAppsState */);
     }
 
     private @NonNull RectF getDefaultWindowTargetRect() {
diff --git a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
index d37e530..0284ae4 100644
--- a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
@@ -194,7 +194,7 @@
     @Override
     protected void onDraw(Canvas canvas) {
         if (mDividerType == DividerType.LINE) {
-            int l = (getWidth() - getPaddingLeft() - mDividerSize[0]) / 2;
+            int l = (getWidth() - mDividerSize[0]) / 2;
             int t = getHeight() - (getPaddingBottom() / 2);
             int radius = mDividerSize[1];
             canvas.drawRoundRect(l, t, l + mDividerSize[0], t + mDividerSize[1], radius, radius,
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 768a348..793d987 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -27,6 +27,7 @@
 import android.view.WindowManagerGlobal;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.DeviceProfile;
@@ -118,6 +119,24 @@
     }
 
     /**
+     * Enables manual taskbar stashing. This method should only be used for tests that need to
+     * stash/unstash the taskbar.
+     */
+    @VisibleForTesting
+    public void enableManualStashingForTests(boolean enableManualStashing) {
+        mControllers.taskbarStashController.enableManualStashingForTests(enableManualStashing);
+    }
+
+    /**
+     * Unstashes the Taskbar if it is stashed. This method should only be used to unstash the
+     * taskbar at the end of a test.
+     */
+    @VisibleForTesting
+    public void unstashTaskbarIfStashed() {
+        mControllers.taskbarStashController.onLongPressToUnstashTaskbar();
+    }
+
+    /**
      * Should be called from onResume() and onPause(), and animates the Taskbar accordingly.
      */
     public void onLauncherResumedOrPaused(boolean isResumed) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 5387b1a..6c74ae3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -71,6 +71,8 @@
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.popup.PopupDataProvider;
 import com.android.launcher3.taskbar.allapps.TaskbarAllAppsController;
+import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.NavigationMode;
@@ -646,12 +648,16 @@
                         Toast.makeText(this, R.string.safemode_shortcut_error,
                                 Toast.LENGTH_SHORT).show();
                     } else  if (info.isPromise()) {
+                        TestLogging.recordEvent(
+                                TestProtocol.SEQUENCE_MAIN, "start: taskbarPromiseIcon");
                         intent = new PackageManagerHelper(this)
                                 .getMarketIntent(info.getTargetPackage())
                                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                         startActivity(intent);
 
                     } else if (info.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+                        TestLogging.recordEvent(
+                                TestProtocol.SEQUENCE_MAIN, "start: taskbarDeepShortcut");
                         String id = info.getDeepShortcutId();
                         String packageName = intent.getPackage();
                         getSystemService(LauncherApps.class)
@@ -680,6 +686,7 @@
         Intent intent = new Intent(info.getIntent())
                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         try {
+            TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "start: taskbarAppIcon");
             if (info.user.equals(Process.myUserHandle())) {
                 // TODO(b/216683257): Use startActivityForResult for search results that require it.
                 startActivity(intent);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
index d2e24e5..a5999cc 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -116,7 +116,7 @@
         taskbarEduController.init(this);
         taskbarPopupController.init(this);
         taskbarForceVisibleImmersiveController.init(this);
-        taskbarAllAppsController.init(this);
+        taskbarAllAppsController.init(this, sharedState);
 
         mControllersToLog = new LoggableTaskbarController[] {
                 taskbarDragController, navButtonController, navbarButtonsViewController,
@@ -152,6 +152,7 @@
         taskbarAutohideSuspendController.onDestroy();
         taskbarPopupController.onDestroy();
         taskbarForceVisibleImmersiveController.onDestroy();
+        taskbarAllAppsController.onDestroy();
 
         mControllersToLog = null;
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index af98b7f..e2ab443 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -63,6 +63,8 @@
 import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.shortcuts.DeepShortcutView;
 import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
+import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.systemui.shared.recents.model.Task;
@@ -128,7 +130,7 @@
         if (!(view instanceof BubbleTextView)) {
             return false;
         }
-
+        TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onTaskbarItemLongClick");
         BubbleTextView btv = (BubbleTextView) view;
         mActivity.onDragStart();
         btv.post(() -> {
@@ -164,24 +166,7 @@
         dragLayerY += dragRect.top;
 
         DragOptions dragOptions = new DragOptions();
-        dragOptions.preDragCondition = new DragOptions.PreDragCondition() {
-            private DragView mDragView;
-
-            @Override
-            public boolean shouldStartDrag(double distanceDragged) {
-                return mDragView != null && mDragView.isAnimationFinished();
-            }
-
-            @Override
-            public void onPreDragStart(DropTarget.DragObject dragObject) {
-                mDragView = dragObject.dragView;
-            }
-
-            @Override
-            public void onPreDragEnd(DropTarget.DragObject dragObject, boolean dragStarted) {
-                mDragView = null;
-            }
-        };
+        dragOptions.preDragCondition = null;
         if (FeatureFlags.ENABLE_TASKBAR_POPUP_MENU.get()) {
             PopupContainerWithArrow<BaseTaskbarContext> popupContainer =
                     mControllers.taskbarPopupController.showForIcon(btv);
@@ -189,6 +174,32 @@
                 dragOptions.preDragCondition = popupContainer.createPreDragCondition(false);
             }
         }
+        if (dragOptions.preDragCondition == null) {
+            dragOptions.preDragCondition = new DragOptions.PreDragCondition() {
+                private DragView mDragView;
+
+                @Override
+                public boolean shouldStartDrag(double distanceDragged) {
+                    return mDragView != null && mDragView.isAnimationFinished();
+                }
+
+                @Override
+                public void onPreDragStart(DropTarget.DragObject dragObject) {
+                    mDragView = dragObject.dragView;
+
+                    if (FeatureFlags.ENABLE_TASKBAR_POPUP_MENU.get()
+                            && !shouldStartDrag(0)) {
+                        // Immediately close the popup menu.
+                        mDragView.setOnAnimationEndCallback(() -> callOnDragStart());
+                    }
+                }
+
+                @Override
+                public void onPreDragEnd(DropTarget.DragObject dragObject, boolean dragStarted) {
+                    mDragView = null;
+                }
+            };
+        }
 
         return startDrag(
                 drawable,
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
index cdac497..c7330d3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.taskbar;
 
 import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
+import static com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_ALL_APPS;
 import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_CONTENT;
 import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_FRAME;
 import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_REGION;
@@ -184,6 +185,9 @@
             } else if (mControllers.taskbarDragController.isSystemDragInProgress()) {
                 // Let touches pass through us.
                 insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
+            } else if (AbstractFloatingView.getOpenView(mActivity, TYPE_TASKBAR_ALL_APPS) != null) {
+                // Let touches pass through us.
+                insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
             } else if (mControllers.taskbarViewController.areIconsVisible()
                     || AbstractFloatingView.getOpenView(mActivity, TYPE_ALL) != null
                     || mActivity.isNavBarKidsModeActive()) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 235a156..ebe6a04 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -29,16 +29,13 @@
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseQuickstepLauncher;
-import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.quickstep.AnimatedFloat;
 import com.android.quickstep.RecentsAnimationCallbacks;
 import com.android.quickstep.RecentsAnimationController;
 import com.android.quickstep.views.RecentsView;
-import com.android.systemui.animation.ViewRootSync;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 
 import java.util.HashMap;
@@ -79,9 +76,6 @@
 
     private boolean mShouldDelayLauncherStateAnim;
 
-    // We skip any view synchronizations during init/destroy.
-    private boolean mCanSyncViews;
-
     private final StateManager.StateListener<LauncherState> mStateListener =
             new StateManager.StateListener<LauncherState>() {
 
@@ -108,8 +102,6 @@
             };
 
     public void init(TaskbarControllers controllers, BaseQuickstepLauncher launcher) {
-        mCanSyncViews = false;
-
         mControllers = controllers;
         mLauncher = launcher;
 
@@ -129,13 +121,9 @@
         updateStateForFlag(FLAG_RESUMED, launcher.hasBeenResumed());
         mLauncherState = launcher.getStateManager().getState();
         applyState(0);
-
-        mCanSyncViews = true;
     }
 
     public void onDestroy() {
-        mCanSyncViews = false;
-
         mIconAlignmentForResumedState.finishAnimation();
         mIconAlignmentForGestureState.finishAnimation();
         mIconAlignmentForLauncherState.finishAnimation();
@@ -143,8 +131,6 @@
         mIconAlphaForHome.setConsumer(null);
         mLauncher.getHotseat().setIconsAlpha(1f);
         mLauncher.getStateManager().removeStateListener(mStateListener);
-
-        mCanSyncViews = true;
     }
 
     public Animator createAnimToLauncher(@NonNull LauncherState toState,
@@ -394,27 +380,6 @@
             return;
         }
         float alignment = alignmentSupplier.get();
-        float currentValue = mIconAlphaForHome.getValue();
-        boolean taskbarWillBeVisible = alignment < 1;
-        boolean firstFrameVisChanged = (taskbarWillBeVisible && Float.compare(currentValue, 1) != 0)
-                || (!taskbarWillBeVisible && Float.compare(currentValue, 0) != 0);
-
-        // Sync the first frame where we swap taskbar and hotseat.
-        if (firstFrameVisChanged && mCanSyncViews && !Utilities.IS_RUNNING_IN_TEST_HARNESS) {
-            DeviceProfile dp = mLauncher.getDeviceProfile();
-
-            // Do all the heavy work before the sync.
-            mControllers.taskbarViewController.createIconAlignmentControllerIfNotExists(dp);
-
-            ViewRootSync.synchronizeNextDraw(mLauncher.getHotseat(),
-                    mControllers.taskbarActivityContext.getDragLayer(),
-                    () -> updateIconAlignment(alignment));
-        } else {
-            updateIconAlignment(alignment);
-        }
-    }
-
-    private void updateIconAlignment(float alignment) {
         mControllers.taskbarViewController.setLauncherIconAlignment(
                 alignment, mLauncher.getDeviceProfile());
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
index ea4fe34..f9c8062 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
@@ -145,6 +145,7 @@
                 });
         // TODO (b/198438631): configure for taskbar/context
         container.setPopupItemDragHandler(new TaskbarPopupItemDragHandler());
+        mControllers.taskbarDragController.addDragListener(container);
 
         container.populateAndShow(icon,
                 mPopupDataProvider.getShortcutCountForItem(item),
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java
index 23beef0..a5c55b0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java
@@ -24,4 +24,6 @@
 
     public boolean setupUIVisible = false;
 
+    public boolean allAppsVisible = false;
+
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 473be9e..f9a282b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -136,6 +136,8 @@
     private boolean mIsSystemGestureInProgress;
     private boolean mIsImeShowing;
 
+    private boolean mEnableManualStashingForTests = false;
+
     // Evaluate whether the handle should be stashed
     private final StatePropertyHolder mStatePropertyHolder = new StatePropertyHolder(
             flags -> {
@@ -199,12 +201,15 @@
      */
     protected boolean supportsManualStashing() {
         return supportsVisualStashing()
-                && (!Utilities.IS_RUNNING_IN_TEST_HARNESS || supportsStashingForTests());
+                && (!Utilities.IS_RUNNING_IN_TEST_HARNESS || mEnableManualStashingForTests);
     }
 
-    private boolean supportsStashingForTests() {
-        // TODO: enable this for tests that specifically check stash/unstash behavior.
-        return false;
+    /**
+     * Enables support for manual stashing. This should only be used to add this functionality
+     * to Launcher specific tests.
+     */
+    public void enableManualStashingForTests(boolean enableManualStashing) {
+        mEnableManualStashingForTests = enableManualStashing;
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 1320060..a89061b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -183,23 +183,15 @@
     }
 
     /**
-     * Creates the icon alignment controller if it does not already exist.
-     * @param launcherDp Launcher device profile.
-     */
-    public void createIconAlignmentControllerIfNotExists(DeviceProfile launcherDp) {
-        if (mIconAlignControllerLazy == null) {
-            mIconAlignControllerLazy = createIconAlignmentController(launcherDp);
-        }
-    }
-
-    /**
      * Sets the taskbar icon alignment relative to Launcher hotseat icons
      * @param alignmentRatio [0, 1]
      *                       0 => not aligned
      *                       1 => fully aligned
      */
     public void setLauncherIconAlignment(float alignmentRatio, DeviceProfile launcherDp) {
-        createIconAlignmentControllerIfNotExists(launcherDp);
+        if (mIconAlignControllerLazy == null) {
+            mIconAlignControllerLazy = createIconAlignmentController(launcherDp);
+        }
         mIconAlignControllerLazy.setPlayFraction(alignmentRatio);
         if (alignmentRatio <= 0 || alignmentRatio >= 1) {
             // Cleanup lazy controller so that it is created again in next animation
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContext.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContext.java
index a67ca70..22fffdf 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContext.java
@@ -24,6 +24,7 @@
 import android.content.Context;
 import android.graphics.Insets;
 import android.view.KeyEvent;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.WindowInsets;
 
@@ -38,6 +39,8 @@
 import com.android.launcher3.taskbar.TaskbarActivityContext;
 import com.android.launcher3.taskbar.TaskbarDragController;
 import com.android.launcher3.taskbar.TaskbarStashController;
+import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.OnboardingPrefs;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.views.BaseDragLayer;
@@ -166,7 +169,6 @@
             super.onAttachedToWindow();
             ViewTreeObserverWrapper.addOnComputeInsetsListener(
                     getViewTreeObserver(), this);
-            mActivity.mAllAppsViewController.show();
         }
 
         @Override
@@ -181,6 +183,12 @@
         }
 
         @Override
+        public boolean dispatchTouchEvent(MotionEvent ev) {
+            TestLogging.recordMotionEvent(TestProtocol.SEQUENCE_MAIN, "Touch event", ev);
+            return super.dispatchTouchEvent(ev);
+        }
+
+        @Override
         public boolean dispatchKeyEvent(KeyEvent event) {
             if (event.getAction() == ACTION_UP && event.getKeyCode() == KEYCODE_BACK) {
                 AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
index cf9d778..044459e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
@@ -38,6 +38,7 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.taskbar.TaskbarActivityContext;
 import com.android.launcher3.taskbar.TaskbarControllers;
+import com.android.launcher3.taskbar.TaskbarSharedState;
 
 import java.util.List;
 import java.util.Optional;
@@ -62,6 +63,7 @@
     private final LayoutParams mLayoutParams;
 
     private TaskbarControllers mControllers;
+    private TaskbarSharedState mSharedState;
     /** Window context for all apps if it is open. */
     private @Nullable TaskbarAllAppsContext mAllAppsContext;
 
@@ -77,9 +79,19 @@
     }
 
     /** Initialize the controller. */
-    public void init(TaskbarControllers controllers) {
-        if (FeatureFlags.ENABLE_ALL_APPS_IN_TASKBAR.get()) {
-            mControllers = controllers;
+    public void init(TaskbarControllers controllers, TaskbarSharedState sharedState) {
+        if (!FeatureFlags.ENABLE_ALL_APPS_IN_TASKBAR.get()) {
+            return;
+        }
+        mControllers = controllers;
+        mSharedState = sharedState;
+
+        /*
+         * Recreate All Apps if it was open in the previous Taskbar instance (e.g. the configuration
+         * changed).
+         */
+        if (mSharedState.allAppsVisible) {
+            show(false);
         }
     }
 
@@ -112,10 +124,15 @@
 
     /** Opens the {@link TaskbarAllAppsContainerView} in a new window. */
     public void show() {
+        show(true);
+    }
+
+    private void show(boolean animate) {
         if (mProxyView.isOpen()) {
             return;
         }
         mProxyView.show();
+        mSharedState.allAppsVisible = true;
 
         mAllAppsContext = new TaskbarAllAppsContext(mTaskbarContext,
                 this,
@@ -129,6 +146,7 @@
         mAllAppsContext.getAppsView().getFloatingHeaderView()
                 .findFixedRowByType(PredictionRowView.class)
                 .setPredictedApps(mPredictedApps);
+        mAllAppsContext.getAllAppsViewController().show(animate);
     }
 
     /** Closes the {@link TaskbarAllAppsContainerView}. */
@@ -148,6 +166,12 @@
             return;
         }
         mProxyView.close(false);
+        mSharedState.allAppsVisible = false;
+        onDestroy();
+    }
+
+    /** Destroys the controller and any All Apps window if present. */
+    public void onDestroy() {
         mTaskbarContext.removeOnDeviceProfileChangeListener(this);
         Optional.ofNullable(mAllAppsContext)
                 .map(c -> c.getSystemService(WindowManager.class))
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
index 02aa3f2..5d2d72a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
@@ -50,17 +50,21 @@
     }
 
     /** Opens the all apps view. */
-    void show() {
+    void show(boolean animate) {
         if (mIsOpen || mOpenCloseAnimator.isRunning()) {
             return;
         }
         mIsOpen = true;
         attachToContainer();
 
-        mOpenCloseAnimator.setValues(
-                PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
-        mOpenCloseAnimator.setInterpolator(AGGRESSIVE_EASE);
-        mOpenCloseAnimator.setDuration(DEFAULT_OPEN_DURATION).start();
+        if (animate) {
+            mOpenCloseAnimator.setValues(
+                    PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
+            mOpenCloseAnimator.setInterpolator(AGGRESSIVE_EASE);
+            mOpenCloseAnimator.setDuration(DEFAULT_OPEN_DURATION).start();
+        } else {
+            mTranslationShift = TRANSLATION_SHIFT_OPENED;
+        }
     }
 
     /** The apps container inside this view. */
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
index 648c486..4597422 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
@@ -53,8 +53,8 @@
     }
 
     /** Starts the {@link TaskbarAllAppsSlideInView} enter transition. */
-    void show() {
-        mSlideInView.show();
+    void show(boolean animate) {
+        mSlideInView.show(animate);
     }
 
     /** Closes the {@link TaskbarAllAppsSlideInView}. */
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index 0eaea83..84b3839 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -26,12 +26,14 @@
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
 import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
+import static com.android.launcher3.testing.TestProtocol.BAD_STATE;
 import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
 import static com.android.quickstep.views.RecentsView.RECENTS_GRID_PROGRESS;
 import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
 import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
 
 import android.util.FloatProperty;
+import android.util.Log;
 
 import androidx.annotation.NonNull;
 
@@ -65,7 +67,10 @@
         ADJACENT_PAGE_HORIZONTAL_OFFSET.set(mRecentsView, scaleAndOffset[1]);
         TASK_SECONDARY_TRANSLATION.set(mRecentsView, 0f);
 
-        getContentAlphaProperty().set(mRecentsView, state.overviewUi ? 1f : 0);
+        float recentsAlpha = state.overviewUi ? 1f : 0;
+        Log.d(BAD_STATE, "BaseRecentsViewStateController setState state=" + state
+                + ", alpha=" + recentsAlpha);
+        getContentAlphaProperty().set(mRecentsView, recentsAlpha);
         getTaskModalnessProperty().set(mRecentsView, state.getOverviewModalness());
         RECENTS_GRID_PROGRESS.set(mRecentsView,
                 state.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile()) ? 1f : 0f);
@@ -74,6 +79,8 @@
     @Override
     public void setStateWithAnimation(LauncherState toState, StateAnimationConfig config,
             PendingAnimation builder) {
+        Log.d(BAD_STATE, "BaseRecentsViewStateController setStateWithAnimation state=" + toState
+                + ", config.skipOverview=" + config.hasAnimationFlag(SKIP_OVERVIEW));
         if (config.hasAnimationFlag(SKIP_OVERVIEW)) {
             return;
         }
@@ -97,7 +104,10 @@
         setter.setFloat(mRecentsView, TASK_SECONDARY_TRANSLATION, 0f,
                 config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, LINEAR));
 
-        setter.setFloat(mRecentsView, getContentAlphaProperty(), toState.overviewUi ? 1 : 0,
+        float recentsAlpha = toState.overviewUi ? 1 : 0;
+        Log.d(BAD_STATE, "BaseRecentsViewStateController setStateWithAnimationInternal toState="
+                + toState + ", alpha=" + recentsAlpha);
+        setter.setFloat(mRecentsView, getContentAlphaProperty(), recentsAlpha,
                 config.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
 
         setter.setFloat(
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index d8f694e..2ca59eb 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -38,6 +38,7 @@
 import static com.android.launcher3.states.StateAnimationConfig.SKIP_ALL_ANIMATIONS;
 import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
 import static com.android.launcher3.states.StateAnimationConfig.SKIP_SCRIM;
+import static com.android.launcher3.testing.TestProtocol.BAD_STATE;
 import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_RIGHT;
 import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_UP;
 import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
@@ -54,6 +55,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.graphics.PointF;
+import android.util.Log;
 import android.view.MotionEvent;
 import android.view.animation.Interpolator;
 
@@ -224,6 +226,7 @@
         // Set RecentView's initial properties.
         RECENTS_SCALE_PROPERTY.set(mRecentsView, fromState.getOverviewScaleAndOffset(mLauncher)[0]);
         ADJACENT_PAGE_HORIZONTAL_OFFSET.set(mRecentsView, 1f);
+        Log.d(BAD_STATE, "NBQSTC setupOverviewAnimators setContentAlpha=1");
         mRecentsView.setContentAlpha(1);
         mRecentsView.setFullscreenProgress(fromState.getOverviewFullscreenProgress());
         mLauncher.getActionsView().getVisibilityAlpha().setValue(
@@ -242,6 +245,24 @@
                 QUICK_SWITCH.getWorkspaceScrimColor(mLauncher), LINEAR);
         if (mRecentsView.getTaskViewCount() == 0) {
             xAnim.addFloat(mRecentsView, CONTENT_ALPHA, 0f, 1f, LINEAR);
+            Log.d(BAD_STATE, "NBQSTC setupOverviewAnimators from: 0 to: 1");
+            xAnim.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    Log.d(BAD_STATE, "NBQSTC setupOverviewAnimators onStart");
+                }
+
+                @Override
+                public void onAnimationCancel(Animator animation) {
+                    float alpha = mRecentsView == null ? -1 : CONTENT_ALPHA.get(mRecentsView);
+                    Log.d(BAD_STATE, "NBQSTC setupOverviewAnimators onCancel, alpha=" + alpha);
+                }
+
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    Log.d(BAD_STATE, "NBQSTC setupOverviewAnimators onEnd");
+                }
+            });
         }
         mXOverviewAnim = xAnim.createPlaybackController();
         mXOverviewAnim.dispatchOnStart();
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
index e5cd53a..d1b0a9c 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
@@ -29,6 +29,7 @@
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
+import static com.android.launcher3.testing.TestProtocol.BAD_STATE;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK;
 import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
 import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
@@ -36,6 +37,7 @@
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 
+import android.util.Log;
 import android.view.MotionEvent;
 
 import com.android.launcher3.Launcher;
@@ -112,6 +114,7 @@
         RECENTS_SCALE_PROPERTY.set(mOverviewPanel,
                 QUICK_SWITCH.getOverviewScaleAndOffset(mLauncher)[0] * 0.85f);
         ADJACENT_PAGE_HORIZONTAL_OFFSET.set(mOverviewPanel, 1f);
+        Log.d(BAD_STATE, "QuickSwitchTouchController initCurrentAnimation setContentAlpha=1");
         mOverviewPanel.setContentAlpha(1);
 
         mCurrentAnimation = mLauncher.getStateManager()
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index af6cb84..bc7a6ae 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -233,7 +233,8 @@
 
         return mActivity.getFirstMatchForAppClose(launchCookieItemId,
                 runningTaskView.getTask().key.getComponent().getPackageName(),
-                UserHandle.of(runningTaskView.getTask().key.userId));
+                UserHandle.of(runningTaskView.getTask().key.userId),
+                false /* supportsAllAppsState */);
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 3e7ad62..4f0b976 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -24,6 +24,7 @@
 import static com.android.launcher3.Utilities.createHomeIntent;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.graphics.SysUiScrim.SYSUI_PROGRESS;
+import static com.android.launcher3.testing.TestProtocol.BAD_STATE;
 import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
 import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
 import static com.android.quickstep.TaskViewUtils.createRecentsWindowAnimator;
@@ -38,6 +39,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
+import android.util.Log;
 import android.view.Display;
 import android.view.SurfaceControl.Transaction;
 import android.view.View;
@@ -312,6 +314,7 @@
     protected void onStart() {
         // Set the alpha to 1 before calling super, as it may get set back to 0 due to
         // onActivityStart callback.
+        Log.d(BAD_STATE, "RecentsActivity onStart mFallbackRecentsView.setContentAlpha(1)");
         mFallbackRecentsView.setContentAlpha(1);
         super.onStart();
         mFallbackRecentsView.updateLocusId();
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index e67b0a5..516ecd4 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -37,6 +37,7 @@
 import static com.android.launcher3.anim.Interpolators.clampToProgress;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.statehandlers.DepthController.DEPTH;
+import static com.android.launcher3.testing.TestProtocol.BAD_STATE;
 import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
@@ -55,6 +56,7 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.os.Build;
+import android.util.Log;
 import android.view.SurfaceControl;
 import android.view.View;
 import android.window.TransitionInfo;
@@ -577,6 +579,29 @@
             launcherAnim = dp.isTablet
                     ? ObjectAnimator.ofFloat(recentsView, RecentsView.CONTENT_ALPHA, 0)
                     : recentsView.createAdjacentPageAnimForTaskLaunch(taskView);
+            if (dp.isTablet) {
+                Log.d(BAD_STATE, "TVU composeRecentsLaunchAnimator alpha=" + 0);
+                launcherAnim.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationStart(Animator animation) {
+                        Log.d(BAD_STATE, "TVU composeRecentsLaunchAnimator onStart");
+                    }
+
+                    @Override
+                    public void onAnimationCancel(Animator animation) {
+                        float alpha = recentsView == null
+                                ? -1
+                                : RecentsView.CONTENT_ALPHA.get(recentsView);
+                        Log.d(BAD_STATE, "TVU composeRecentsLaunchAnimator onCancel, alpha="
+                                + alpha);
+                    }
+
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        Log.d(BAD_STATE, "TVU composeRecentsLaunchAnimator onEnd");
+                    }
+                });
+            }
             launcherAnim.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
             launcherAnim.setDuration(RECENTS_LAUNCH_DURATION);
 
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index c7a8382..e6f73dc 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -15,6 +15,7 @@
  */
 package com.android.quickstep.fallback;
 
+import static com.android.launcher3.testing.TestProtocol.BAD_STATE;
 import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
 import static com.android.quickstep.fallback.RecentsState.DEFAULT;
 import static com.android.quickstep.fallback.RecentsState.HOME;
@@ -27,6 +28,7 @@
 import android.content.Context;
 import android.os.Build;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.MotionEvent;
 
 import androidx.annotation.Nullable;
@@ -221,6 +223,7 @@
         setOverviewStateEnabled(true);
         setOverviewGridEnabled(toState.displayOverviewTasksAsGrid(mActivity.getDeviceProfile()));
         setOverviewFullscreenEnabled(toState.isFullScreen());
+        Log.d(BAD_STATE, "FRV onStateTransitionStart setFreezeVisibility=true, toState=" + toState);
         setFreezeViewVisibility(true);
     }
 
@@ -232,6 +235,8 @@
         }
         boolean isOverlayEnabled = finalState == DEFAULT || finalState == MODAL_TASK;
         setOverlayEnabled(isOverlayEnabled);
+        Log.d(BAD_STATE, "FRV onStateTransitionComplete setFreezeVisibility=false, finalState="
+                + finalState);
         setFreezeViewVisibility(false);
 
         if (isOverlayEnabled) {
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
index 2ddbd97..3c88988 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
@@ -64,7 +64,7 @@
 
     private static final String TAG = "TutorialController";
 
-    private static final float FINGER_DOT_VISIBLE_ALPHA = 0.6f;
+    private static final float FINGER_DOT_VISIBLE_ALPHA = 0.7f;
     private static final float FINGER_DOT_SMALL_SCALE = 0.7f;
     private static final int FINGER_DOT_ANIMATION_DURATION_MILLIS = 500;
 
diff --git a/quickstep/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.java b/quickstep/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.java
index 5c72c8f..edaa326 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.java
@@ -15,10 +15,13 @@
  */
 package com.android.quickstep.util;
 
+import static com.android.launcher3.testing.TestProtocol.BAD_STATE;
 import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
 
 import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
+import android.util.Log;
 
 import androidx.dynamicanimation.animation.DynamicAnimation;
 
@@ -27,6 +30,8 @@
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.quickstep.views.RecentsView;
 
+import java.util.Arrays;
+
 public class RecentsAtomicAnimationFactory<ACTIVITY_TYPE extends StatefulActivity, STATE_TYPE>
         extends AtomicAnimationFactory<STATE_TYPE> {
 
@@ -46,8 +51,30 @@
     public Animator createStateElementAnimation(int index, float... values) {
         switch (index) {
             case INDEX_RECENTS_FADE_ANIM:
-                return ObjectAnimator.ofFloat(mActivity.getOverviewPanel(),
+                ObjectAnimator alpha = ObjectAnimator.ofFloat(mActivity.getOverviewPanel(),
                         RecentsView.CONTENT_ALPHA, values);
+                Log.d(BAD_STATE, "RAAF createStateElementAnimation alpha="
+                        + Arrays.toString(values));
+                alpha.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationStart(Animator animation) {
+                        Log.d(BAD_STATE, "RAAF createStateElementAnimation onStart");
+                    }
+
+                    @Override
+                    public void onAnimationCancel(Animator animation) {
+                        RecentsView recent = mActivity.getOverviewPanel();
+                        float alpha = recent == null ? -1 : RecentsView.CONTENT_ALPHA.get(recent);
+                        Log.d(BAD_STATE, "RAAF createStateElementAnimation onCancel, alpha="
+                                + alpha);
+                    }
+
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        Log.d(BAD_STATE, "RAAF createStateElementAnimation onEnd");
+                    }
+                });
+                return alpha;
             case INDEX_RECENTS_TRANSLATE_X_ANIM: {
                 RecentsView rv = mActivity.getOverviewPanel();
                 return new SpringAnimationBuilder(mActivity)
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index e8f3324..8d717d2 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -21,11 +21,13 @@
 import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
 import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
 import static com.android.launcher3.LauncherState.SPRING_LOADED;
+import static com.android.launcher3.testing.TestProtocol.BAD_STATE;
 
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.os.Build;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.MotionEvent;
 import android.view.Surface;
 
@@ -65,6 +67,7 @@
     public void init(OverviewActionsView actionsView,
             SplitSelectStateController splitPlaceholderView) {
         super.init(actionsView, splitPlaceholderView);
+        Log.d(BAD_STATE, "LauncherRecentsView init setContentAlpha=0");
         setContentAlpha(0);
     }
 
@@ -97,6 +100,7 @@
         setOverviewStateEnabled(toState.overviewUi);
         setOverviewGridEnabled(toState.displayOverviewTasksAsGrid(mActivity.getDeviceProfile()));
         setOverviewFullscreenEnabled(toState.getOverviewFullscreenProgress() == 1);
+        Log.d(BAD_STATE, "LRV onStateTransitionStart setFreezeVisibility=true, toState=" + toState);
         setFreezeViewVisibility(true);
     }
 
@@ -108,6 +112,8 @@
         }
         boolean isOverlayEnabled = finalState == OVERVIEW || finalState == OVERVIEW_MODAL_TASK;
         setOverlayEnabled(isOverlayEnabled);
+        Log.d(BAD_STATE, "LRV onStateTransitionComplete setFreezeVisibility=false, finalState="
+                + finalState);
         setFreezeViewVisibility(false);
 
         if (isOverlayEnabled) {
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java b/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java
new file mode 100644
index 0000000..ba93975
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2022 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 androidx.test.InstrumentationRegistry.getInstrumentation;
+
+import static junit.framework.TestCase.assertEquals;
+
+import android.content.Intent;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.launcher3.tapl.Taskbar;
+import com.android.launcher3.ui.TaplTestsLauncher3;
+
+import org.junit.After;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class TaplTestsTaskbar extends AbstractQuickStepTest {
+
+    private static final String TEST_APP_NAME = "LauncherTestApp";
+    private static final String TEST_APP_PACKAGE =
+            getInstrumentation().getContext().getPackageName();
+    private static final String CALCULATOR_APP_PACKAGE =
+            resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR);
+
+    @Override
+    public void setUp() throws Exception {
+        Assume.assumeTrue(mLauncher.isTablet());
+        super.setUp();
+        mLauncher.useTestWorkspaceLayoutOnReload();
+        TaplTestsLauncher3.initialize(this);
+
+        startAppFast(CALCULATOR_APP_PACKAGE);
+        mLauncher.showTaskbarIfHidden();
+    }
+
+    @After
+    public void tearDown() {
+        mLauncher.useDefaultWorkspaceLayoutOnReload();
+    }
+
+    @Test
+    public void testHideShowTaskbar() {
+        getTaskbar().hide();
+        mLauncher.getLaunchedAppState().showTaskbar();
+    }
+
+    @Test
+    public void testLaunchApp() throws Exception {
+        getTaskbar().getAppIcon(TEST_APP_NAME).launch(TEST_APP_PACKAGE);
+    }
+
+    @Test
+    public void testOpenMenu() throws Exception {
+        getTaskbar().getAppIcon(TEST_APP_NAME).openMenu();
+    }
+
+    @Test
+    public void testLaunchShortcut() throws Exception {
+        getTaskbar().getAppIcon(TEST_APP_NAME)
+                .openDeepShortcutMenu()
+                .getMenuItem("Shortcut 1")
+                .launch(TEST_APP_PACKAGE);
+    }
+
+    @Test
+    @PortraitLandscape
+    public void testLaunchAppInSplitscreen() throws Exception {
+        getTaskbar().getAppIcon(TEST_APP_NAME).dragToSplitscreen(
+                TEST_APP_PACKAGE, CALCULATOR_APP_PACKAGE);
+    }
+
+    @Test
+    @PortraitLandscape
+    public void testLaunchShortcutInSplitscreen() throws Exception {
+        getTaskbar().getAppIcon(TEST_APP_NAME)
+                .openDeepShortcutMenu()
+                .getMenuItem("Shortcut 1")
+                .dragToSplitscreen(TEST_APP_PACKAGE, CALCULATOR_APP_PACKAGE);
+    }
+
+    @Test
+    public void testLaunchApp_FromTaskbarAllApps() throws Exception {
+        getTaskbar().openAllApps().getAppIcon(TEST_APP_NAME).launch(TEST_APP_PACKAGE);
+    }
+
+    @Test
+    public void testOpenMenu_FromTaskbarAllApps() throws Exception {
+        getTaskbar().openAllApps().getAppIcon(TEST_APP_NAME).openMenu();
+    }
+
+    @Test
+    public void testLaunchShortcut_FromTaskbarAllApps() throws Exception {
+        getTaskbar().openAllApps()
+                .getAppIcon(TEST_APP_NAME)
+                .openDeepShortcutMenu()
+                .getMenuItem("Shortcut 1")
+                .launch(TEST_APP_PACKAGE);
+    }
+
+    @Test
+    @PortraitLandscape
+    public void testLaunchAppInSplitscreen_FromTaskbarAllApps() throws Exception {
+        getTaskbar().openAllApps()
+                .getAppIcon(TEST_APP_NAME)
+                .dragToSplitscreen(TEST_APP_PACKAGE, CALCULATOR_APP_PACKAGE);
+    }
+
+    @Test
+    @PortraitLandscape
+    public void testLaunchShortcutInSplitscreen_FromTaskbarAllApps() throws Exception {
+        getTaskbar().openAllApps()
+                .getAppIcon(TEST_APP_NAME)
+                .openDeepShortcutMenu()
+                .getMenuItem("Shortcut 1")
+                .dragToSplitscreen(TEST_APP_PACKAGE, CALCULATOR_APP_PACKAGE);
+    }
+
+    private Taskbar getTaskbar() {
+        Taskbar taskbar = mLauncher.getLaunchedAppState().getTaskbar();
+        List<String> taskbarIconNames = taskbar.getIconNames();
+        List<String> hotseatIconNames = mLauncher.getHotseatIconNames();
+
+        assertEquals("Taskbar and hotseat icon counts do not match",
+                taskbarIconNames.size(), hotseatIconNames.size());
+
+        for (int i = 0; i < taskbarIconNames.size(); i++) {
+            assertEquals("Taskbar and Hotseat icons do not match",
+                    taskbarIconNames, hotseatIconNames);
+        }
+
+        return taskbar;
+    }
+}
diff --git a/res/values-v31/styles.xml b/res/values-v31/styles.xml
index e42d0a3..008a77c 100644
--- a/res/values-v31/styles.xml
+++ b/res/values-v31/styles.xml
@@ -38,6 +38,7 @@
         <item name="preferenceScreenStyle">@style/HomeSettings.PreferenceScreenStyle</item>
         <item name="preferenceStyle">@style/HomeSettings.PreferenceStyle</item>
         <item name="switchPreferenceStyle">@style/HomeSettings.SwitchPreferenceStyle</item>
+        <item name="android:fontFamily">google-sans-text</item>
     </style>
 
     <style name="HomeSettings.CategoryStyle" parent="@style/Preference.Category.Material">
diff --git a/res/xml/default_test_workspace.xml b/res/xml/default_test_workspace.xml
new file mode 100644
index 0000000..bd718b3
--- /dev/null
+++ b/res/xml/default_test_workspace.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+
+<!-- Split display specific version of Launcher3/res/xml/default_workspace_4x4.xml -->
+<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3" >
+
+    <!-- Launcher Test Activity -->
+    <resolve
+        launcher:container="-101"
+        launcher:screen="0"
+        launcher:x="0"
+        launcher:y="0" >
+        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.CATEGORY_TEST;component=com.google.android.apps.nexuslauncher.tests/com.android.launcher3.testcomponent.BaseTestingActivity;end" />
+    </resolve>
+
+</favorites>
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 07d7b90..5c406bd 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -57,6 +57,7 @@
 import static com.android.launcher3.popup.SystemShortcut.WIDGETS;
 import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
 import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
+import static com.android.launcher3.testing.TestProtocol.BAD_STATE;
 import static com.android.launcher3.util.ItemInfoMatcher.forFolderMatch;
 
 import android.animation.Animator;
@@ -501,6 +502,8 @@
 
         if (!mModel.addCallbacksAndLoad(this)) {
             if (!internalStateHandled) {
+                Log.d(BAD_STATE, "Launcher onCreate not binding sync, setting DragLayer alpha "
+                        + "ALPHA_INDEX_LAUNCHER_LOAD to 0");
                 // If we are not binding synchronously, show a fade in animation when
                 // the first page bind completes.
                 mDragLayer.getAlphaProperty(ALPHA_INDEX_LAUNCHER_LOAD).setValue(0);
@@ -519,6 +522,8 @@
                         final AlphaProperty property =
                                 mDragLayer.getAlphaProperty(ALPHA_INDEX_LAUNCHER_LOAD);
                         if (property.getValue() == 0) {
+                            Log.d(BAD_STATE, "Launcher onPreDraw ALPHA_INDEX_LAUNCHER_LOAD not"
+                                    + " started yet, cancelling draw.");
                             // Animation haven't started yet; suspend.
                             return false;
                         } else {
@@ -2685,6 +2690,28 @@
         AlphaProperty property = mDragLayer.getAlphaProperty(ALPHA_INDEX_LAUNCHER_LOAD);
         if (property.getValue() < 1) {
             ObjectAnimator anim = ObjectAnimator.ofFloat(property, MultiValueAlpha.VALUE, 1);
+
+            Log.d(BAD_STATE, "Launcher onInitialBindComplete toAlpha=" + 1);
+            anim.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    Log.d(BAD_STATE, "Launcher onInitialBindComplete onStart");
+                }
+
+                @Override
+                public void onAnimationCancel(Animator animation) {
+                    float alpha = mDragLayer == null
+                            ? -1
+                            : mDragLayer.getAlphaProperty(ALPHA_INDEX_LAUNCHER_LOAD).getValue();
+                    Log.d(BAD_STATE, "Launcher onInitialBindComplete onCancel, alpha=" + alpha);
+                }
+
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    Log.d(BAD_STATE, "Launcher onInitialBindComplete onEnd");
+                }
+            });
+
             anim.addListener(AnimatorListeners.forEndCallback(executor::onLoadAnimationCompleted));
             anim.start();
         } else {
@@ -2747,8 +2774,11 @@
      * @param preferredItemId The id of the preferred item to match to if it exists.
      * @param packageName The package name of the app to match.
      * @param user The user of the app to match.
+     * @param supportsAllAppsState If true and we are in All Apps state, looks for view in All Apps.
+     *                             Else we only looks on the workspace.
      */
-    public View getFirstMatchForAppClose(int preferredItemId, String packageName, UserHandle user) {
+    public View getFirstMatchForAppClose(int preferredItemId, String packageName, UserHandle user,
+            boolean supportsAllAppsState) {
         final ItemInfoMatcher preferredItem = (info, cn) ->
                 info != null && info.id == preferredItemId;
         final ItemInfoMatcher packageAndUserAndApp = (info, cn) ->
@@ -2759,7 +2789,7 @@
                         && TextUtils.equals(info.getTargetComponent().getPackageName(),
                         packageName);
 
-        if (isInState(LauncherState.ALL_APPS)) {
+        if (supportsAllAppsState && isInState(LauncherState.ALL_APPS)) {
             return getFirstMatch(Collections.singletonList(mAppsView.getActiveRecyclerView()),
                     preferredItem, packageAndUserAndApp);
         } else {
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index e004a4b..85ee8bc 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -102,6 +102,8 @@
     public static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".settings";
     public static final String KEY_LAYOUT_PROVIDER_AUTHORITY = "KEY_LAYOUT_PROVIDER_AUTHORITY";
 
+    private static final int TEST_WORKSPACE_LAYOUT_RES_XML = R.xml.default_test_workspace;
+
     static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED";
 
     protected DatabaseHelper mOpenHelper;
@@ -109,6 +111,8 @@
 
     private long mLastRestoreTimestamp = 0L;
 
+    private boolean mUseTestWorkspaceLayout;
+
     /**
      * $ adb shell dumpsys activity provider com.android.launcher3
      */
@@ -390,6 +394,14 @@
                 mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
                 return null;
             }
+            case LauncherSettings.Settings.METHOD_SET_USE_TEST_WORKSPACE_LAYOUT_FLAG: {
+                mUseTestWorkspaceLayout = true;
+                return null;
+            }
+            case LauncherSettings.Settings.METHOD_CLEAR_USE_TEST_WORKSPACE_LAYOUT_FLAG: {
+                mUseTestWorkspaceLayout = false;
+                return null;
+            }
             case LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES: {
                 loadDefaultFavoritesIfNecessary();
                 return null;
@@ -609,7 +621,8 @@
 
     private DefaultLayoutParser getDefaultLayoutParser(AppWidgetHost widgetHost) {
         InvariantDeviceProfile idp = LauncherAppState.getIDP(getContext());
-        int defaultLayout = idp.defaultLayoutId;
+        int defaultLayout = mUseTestWorkspaceLayout
+                ? TEST_WORKSPACE_LAYOUT_RES_XML : idp.defaultLayoutId;
 
         if (getContext().getSystemService(UserManager.class).isDemoUser()
                 && idp.demoModeLayoutId != 0) {
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 048aaaa..66195f3 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -374,6 +374,12 @@
 
         public static final String METHOD_CREATE_EMPTY_DB = "create_empty_db";
 
+        public static final String METHOD_SET_USE_TEST_WORKSPACE_LAYOUT_FLAG =
+                "set_use_test_workspace_layout_flag";
+
+        public static final String METHOD_CLEAR_USE_TEST_WORKSPACE_LAYOUT_FLAG =
+                "clear_use_test_workspace_layout_flag";
+
         public static final String METHOD_LOAD_DEFAULT_FAVORITES = "load_default_favorites";
 
         public static final String METHOD_REMOVE_GHOST_WIDGETS = "remove_ghost_widgets";
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index df97d2f..8b19a19 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -30,6 +30,7 @@
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPELEFT;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPERIGHT;
+import static com.android.launcher3.testing.TestProtocol.BAD_STATE;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -1266,6 +1267,7 @@
         // different effects based on device performance. On at least one relatively high-end
         // device I've tried, translating the launcher causes things to get quite laggy.
         mLauncher.getDragLayer().setTranslationX(transX);
+        Log.d(BAD_STATE, "Workspace onOverlayScrollChanged DragLayer ALPHA_INDEX_OVERLAY=" + alpha);
         mLauncher.getDragLayer().getAlphaProperty(ALPHA_INDEX_OVERLAY).setValue(alpha);
     }
 
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index b914ae2..a3945fd 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -98,6 +98,7 @@
     final ValueAnimator mAnim;
     // Whether mAnim has started. Unlike mAnim.isStarted(), this is true even after mAnim ends.
     private boolean mAnimStarted;
+    private Runnable mOnAnimEndCallback = null;
 
     private int mLastTouchX;
     private int mLastTouchY;
@@ -180,6 +181,14 @@
             public void onAnimationStart(Animator animation) {
                 mAnimStarted = true;
             }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                super.onAnimationEnd(animation);
+                if (mOnAnimEndCallback != null) {
+                    mOnAnimEndCallback.run();
+                }
+            }
         });
 
         setDragRegion(new Rect(0, 0, width, height));
@@ -199,6 +208,10 @@
         setWillNotDraw(false);
     }
 
+    public void setOnAnimationEndCallback(Runnable callback) {
+        mOnAnimEndCallback = callback;
+    }
+
     /**
      * Initialize {@code #mIconDrawable} if the item can be represented using
      * an {@link AdaptiveIconDrawable} or {@link FolderAdaptiveIcon}.
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 05fe182..faf5817 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -82,6 +82,10 @@
     public static final String REQUEST_IS_LAUNCHER_INITIALIZED = "is-launcher-initialized";
     public static final String REQUEST_FREEZE_APP_LIST = "freeze-app-list";
     public static final String REQUEST_UNFREEZE_APP_LIST = "unfreeze-app-list";
+    public static final String REQUEST_ENABLE_MANUAL_TASKBAR_STASHING = "enable-taskbar-stashing";
+    public static final String REQUEST_DISABLE_MANUAL_TASKBAR_STASHING = "disable-taskbar-stashing";
+    public static final String REQUEST_UNSTASH_TASKBAR_IF_STASHED = "unstash-taskbar-if-stashed";
+    public static final String REQUEST_STASHED_TASKBAR_HEIGHT = "stashed-taskbar-height";
     public static final String REQUEST_APP_LIST_FREEZE_FLAGS = "app-list-freeze-flags";
     public static final String REQUEST_APPS_LIST_SCROLL_Y = "apps-list-scroll-y";
     public static final String REQUEST_WIDGETS_SCROLL_Y = "widgets-scroll-y";
@@ -96,6 +100,10 @@
     public static final String REQUEST_GET_HAD_NONTEST_EVENTS = "get-had-nontest-events";
     public static final String REQUEST_STOP_EVENT_LOGGING = "stop-event-logging";
     public static final String REQUEST_CLEAR_DATA = "clear-data";
+    public static final String REQUEST_USE_TEST_WORKSPACE_LAYOUT = "use-test-workspace-layout";
+    public static final String REQUEST_USE_DEFAULT_WORKSPACE_LAYOUT =
+            "use-default-workspace-layout";
+    public static final String REQUEST_HOTSEAT_ICON_NAMES = "get-hotseat-icon-names";
     public static final String REQUEST_IS_TABLET = "is-tablet";
     public static final String REQUEST_IS_TWO_PANELS = "is-two-panel";
     public static final String REQUEST_START_DRAG_THRESHOLD = "start-drag-threshold";
@@ -128,4 +136,6 @@
     public static final String NO_DROP_TARGET = "b/195031154";
     public static final String NULL_INT_SET = "b/200572078";
     public static final String MISSING_PROMISE_ICON = "b/202985412";
+
+    public static final String BAD_STATE = "b/223498680";
 }
diff --git a/src/com/android/launcher3/views/FloatingSurfaceView.java b/src/com/android/launcher3/views/FloatingSurfaceView.java
index 7f54d6d..19c28b4 100644
--- a/src/com/android/launcher3/views/FloatingSurfaceView.java
+++ b/src/com/android/launcher3/views/FloatingSurfaceView.java
@@ -159,7 +159,8 @@
             return;
         }
         View icon = mLauncher.getFirstMatchForAppClose(-1,
-                mContract.componentName.getPackageName(), mContract.user);
+                mContract.componentName.getPackageName(), mContract.user,
+                false /* supportsAllAppsState */);
 
         boolean iconChanged = mIcon != icon;
         if (iconChanged) {
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
index b4cd773..9cc3aed 100644
--- a/tests/AndroidManifest-common.xml
+++ b/tests/AndroidManifest-common.xml
@@ -94,7 +94,6 @@
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
-                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
 
@@ -138,6 +137,7 @@
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <intent-filter>
                 <action android:name="com.android.launcher3.intent.action.test_shortcut"/>
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index c99c4f1..136f115 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -567,6 +567,7 @@
                             ordinal == TestProtocol.OVERVIEW_STATE_ORDINAL);
                     break;
                 }
+                case TASKBAR_ALL_APPS:
                 case LAUNCHED_APP: {
                     assertTrue("Launcher is resumed in state: " + expectedContainerType,
                             !isResumed);
@@ -580,9 +581,10 @@
             }
         } else {
             assertTrue(
-                    "Container type is not LAUNCHED_APP or FALLBACK_OVERVIEW: "
-                            + expectedContainerType,
+                    "Container type is not LAUNCHED_APP, TASKBAR_ALL_APPS "
+                            + "or FALLBACK_OVERVIEW: " + expectedContainerType,
                     expectedContainerType == ContainerType.LAUNCHED_APP
+                            || expectedContainerType == ContainerType.TASKBAR_ALL_APPS
                             || expectedContainerType == ContainerType.FALLBACK_OVERVIEW);
         }
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index 3658f41..bfb115d 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -74,7 +74,7 @@
             LauncherInstrumentation.log("hasClickableIcon: icon has insufficient height");
             return false;
         }
-        if (iconCenterInSearchBox(allAppsContainer, icon)) {
+        if (hasSearchBox() && iconCenterInSearchBox(allAppsContainer, icon)) {
             LauncherInstrumentation.log("hasClickableIcon: icon center is under search box");
             return false;
         }
@@ -107,7 +107,7 @@
             final UiObject2 allAppsContainer = verifyActiveContainer();
             final UiObject2 appListRecycler = mLauncher.waitForObjectInContainer(allAppsContainer,
                     "apps_list_view");
-            final UiObject2 searchBox = getSearchBox(allAppsContainer);
+            final UiObject2 searchBox = hasSearchBox() ? getSearchBox(allAppsContainer) : null;
 
             int deviceHeight = mLauncher.getRealDisplaySize().y;
             int bottomGestureStartOnScreen = mLauncher.getBottomGestureStartOnScreen();
@@ -128,8 +128,10 @@
                                                 mLauncher.getVisibleBounds(icon).top
                                                         < bottomGestureStartOnScreen)
                                         .collect(Collectors.toList()),
-                                mLauncher.getVisibleBounds(searchBox).bottom
-                                        - mLauncher.getVisibleBounds(allAppsContainer).top);
+                                hasSearchBox()
+                                        ? mLauncher.getVisibleBounds(searchBox).bottom
+                                        - mLauncher.getVisibleBounds(allAppsContainer).top
+                                        : 0);
                         verifyActiveContainer();
                         final int newScroll = getAllAppsScroll();
                         mLauncher.assertTrue(
@@ -173,18 +175,21 @@
         return appIcon;
     }
 
+    @NonNull
     protected abstract AppIcon createAppIcon(UiObject2 icon);
 
+    protected abstract boolean hasSearchBox();
+
     private void scrollBackToBeginning() {
         try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                 "want to scroll back in all apps")) {
             LauncherInstrumentation.log("Scrolling to the beginning");
             final UiObject2 allAppsContainer = verifyActiveContainer();
-            final UiObject2 searchBox = getSearchBox(allAppsContainer);
+            final UiObject2 searchBox = hasSearchBox() ? getSearchBox(allAppsContainer) : null;
 
             int attempts = 0;
-            final Rect margins =
-                    new Rect(0, mLauncher.getVisibleBounds(searchBox).bottom + 1, 0, 5);
+            final Rect margins = new Rect(
+                    0, hasSearchBox() ? mLauncher.getVisibleBounds(searchBox).bottom + 1 : 0, 0, 5);
 
             for (int scroll = getAllAppsScroll();
                     scroll != 0;
@@ -196,7 +201,11 @@
                         ++attempts <= MAX_SCROLL_ATTEMPTS);
 
                 mLauncher.scroll(
-                        allAppsContainer, Direction.UP, margins, 12, false);
+                        allAppsContainer,
+                        Direction.UP,
+                        margins,
+                        /* steps= */ 12,
+                        /* slowDown= */ false);
             }
 
             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("scrolled up")) {
@@ -225,7 +234,11 @@
             final UiObject2 allAppsContainer = verifyActiveContainer();
             // Start the gesture in the center to avoid starting at elements near the top.
             mLauncher.scroll(
-                    allAppsContainer, Direction.DOWN, new Rect(0, 0, 0, mHeight / 2), 10, false);
+                    allAppsContainer,
+                    Direction.DOWN,
+                    new Rect(0, 0, 0, mHeight / 2),
+                    /* steps= */ 10,
+                    /* slowDown= */ false);
             verifyActiveContainer();
         }
     }
@@ -240,7 +253,11 @@
             final UiObject2 allAppsContainer = verifyActiveContainer();
             // Start the gesture in the center, for symmetry with forward.
             mLauncher.scroll(
-                    allAppsContainer, Direction.UP, new Rect(0, mHeight / 2, 0, 0), 10, false);
+                    allAppsContainer,
+                    Direction.UP,
+                    new Rect(0, mHeight / 2, 0, 0),
+                    /* steps= */ 10,
+                    /*slowDown= */ false);
             verifyActiveContainer();
         }
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/AllAppsFromTaskbar.java b/tests/tapl/com/android/launcher3/tapl/AllAppsFromTaskbar.java
new file mode 100644
index 0000000..5164025
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/AllAppsFromTaskbar.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 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.tapl;
+
+import androidx.annotation.NonNull;
+import androidx.test.uiautomator.UiObject2;
+
+/**
+ * Operations on AllApps opened from the Taskbar.
+ */
+public class AllAppsFromTaskbar extends AllApps {
+
+    AllAppsFromTaskbar(LauncherInstrumentation launcher) {
+        super(launcher);
+    }
+
+    @Override
+    protected LauncherInstrumentation.ContainerType getContainerType() {
+        return LauncherInstrumentation.ContainerType.TASKBAR_ALL_APPS;
+    }
+
+    @NonNull
+    @Override
+    public TaskbarAppIcon getAppIcon(String appName) {
+        return (TaskbarAppIcon) super.getAppIcon(appName);
+    }
+
+    @NonNull
+    @Override
+    protected TaskbarAppIcon createAppIcon(UiObject2 icon) {
+        return new TaskbarAppIcon(mLauncher, icon);
+    }
+
+    @Override
+    protected boolean hasSearchBox() {
+        return false;
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIcon.java b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
index bef242c..e28f0af 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
@@ -51,7 +51,7 @@
     public AppIconMenu openMenu() {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
             return createMenu(mLauncher.clickAndGet(
-                    mObject, "popup_container", getLongClickEvent()));
+                    mObject, /* resName= */ "popup_container", getLongClickEvent()));
         }
     }
 
@@ -61,7 +61,7 @@
     public AppIconMenu openDeepShortcutMenu() {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
             return createMenu(mLauncher.clickAndGet(
-                    mObject, "deep_shortcuts_container", getLongClickEvent()));
+                    mObject, /* resName= */ "deep_shortcuts_container", getLongClickEvent()));
         }
     }
 
@@ -73,8 +73,8 @@
     }
 
     @Override
-    protected String getLongPressIndicator() {
-        return "popup_container";
+    protected void waitForLongPressConfirmation() {
+        mLauncher.waitForLauncherObject("popup_container");
     }
 
     @Override
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java b/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java
index a6a1531..5cf5aba 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java
@@ -41,8 +41,8 @@
     }
 
     @Override
-    protected String getLongPressIndicator() {
-        return "drop_target_bar";
+    protected void waitForLongPressConfirmation() {
+        mLauncher.waitForLauncherObject("drop_target_bar");
     }
 
     @Override
diff --git a/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java b/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java
index 2e00d59..c275f3b 100644
--- a/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java
@@ -35,8 +35,14 @@
         return (AllAppsAppIcon) super.getAppIcon(appName);
     }
 
+    @NonNull
     @Override
     protected HomeAppIcon createAppIcon(UiObject2 icon) {
         return new AllAppsAppIcon(mLauncher, icon);
     }
+
+    @Override
+    protected boolean hasSearchBox() {
+        return true;
+    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/HomeAppIcon.java b/tests/tapl/com/android/launcher3/tapl/HomeAppIcon.java
index baabe12..71d8ba9 100644
--- a/tests/tapl/com/android/launcher3/tapl/HomeAppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/HomeAppIcon.java
@@ -51,8 +51,7 @@
                     () -> {
                         final Rect bounds = target.getDropLocationBounds();
                         return new Point(bounds.centerX(), bounds.centerY());
-                    },
-                    getLongPressIndicator());
+                    });
             FolderIcon result = target.getTargetFolder(dropBounds);
             mLauncher.assertTrue("Can't find the target folder.", result != null);
             return result;
@@ -115,8 +114,12 @@
                      String.format("want to drag the icon to cell(%d, %d)", cellX, cellY))
         ) {
             final Supplier<Point> dest = () -> Workspace.getCellCenter(mLauncher, cellX, cellY);
-            Workspace.dragIconToWorkspace(mLauncher, this, dest, getLongPressIndicator(),
-                    () -> addExpectedEventsForLongClick(), null);
+            Workspace.dragIconToWorkspace(
+                    mLauncher,
+                    /* launchable= */ this,
+                    dest,
+                    () -> addExpectedEventsForLongClick(),
+                    /*expectDropEvents= */ null);
             try (LauncherInstrumentation.Closable ignore = mLauncher.addContextLayer("dragged")) {
                 WorkspaceAppIcon appIcon =
                         (WorkspaceAppIcon) mLauncher.getWorkspace().getWorkspaceAppIcon(mAppName);
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index a28eac6..45a0196 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -97,8 +97,7 @@
         return new LaunchedAppState(mLauncher);
     }
 
-    Point startDrag(long downTime, String longPressIndicator,
-            Runnable expectLongClickEvents, boolean runToSpringLoadedState) {
+    Point startDrag(long downTime, Runnable expectLongClickEvents, boolean runToSpringLoadedState) {
         final Point iconCenter = getObject().getVisibleCenter();
         final Point dragStartCenter = new Point(iconCenter.x,
                 iconCenter.y - getStartDragThreshold());
@@ -108,7 +107,6 @@
                     downTime,
                     iconCenter,
                     dragStartCenter,
-                    longPressIndicator,
                     expectLongClickEvents),
                     SPRING_LOADED_STATE_ORDINAL, "long-pressing and triggering drag start");
         } else {
@@ -116,7 +114,6 @@
                     downTime,
                     iconCenter,
                     dragStartCenter,
-                    longPressIndicator,
                     expectLongClickEvents);
         }
 
@@ -125,21 +122,43 @@
     }
 
     /**
+     * Waits for a confirmation that a long press has successfully been triggered.
+     *
+     * This method waits for a view to either appear or disappear to confirm that the long press
+     * has been triggered and fails if no confirmation is received before the default timeout.
+     */
+    protected abstract void waitForLongPressConfirmation();
+
+    /**
      * Drags this Launchable a short distance before starting a full drag.
      *
      * This is necessary for shortcuts, which require being dragged beyond a threshold to close
      * their container and start drag callbacks.
      */
-    private void movePointerForStartDrag(long downTime, Point iconCenter, Point dragStartCenter,
-            String longPressIndicator, Runnable expectLongClickEvents) {
-        mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN,
-                iconCenter, LauncherInstrumentation.GestureScope.INSIDE);
+    private void movePointerForStartDrag(
+            long downTime,
+            Point iconCenter,
+            Point dragStartCenter,
+            Runnable expectLongClickEvents) {
+        mLauncher.sendPointer(
+                downTime,
+                downTime,
+                MotionEvent.ACTION_DOWN,
+                iconCenter,
+                LauncherInstrumentation.GestureScope.INSIDE);
         LauncherInstrumentation.log("movePointerForStartDrag: sent down");
         expectLongClickEvents.run();
-        mLauncher.waitForLauncherObject(longPressIndicator);
+        waitForLongPressConfirmation();
         LauncherInstrumentation.log("movePointerForStartDrag: indicator");
-        mLauncher.movePointer(iconCenter, dragStartCenter, DEFAULT_DRAG_STEPS, false,
-                downTime, downTime, true, LauncherInstrumentation.GestureScope.INSIDE);
+        mLauncher.movePointer(
+                iconCenter,
+                dragStartCenter,
+                DEFAULT_DRAG_STEPS,
+                /* isDecelerating= */ false,
+                downTime,
+                downTime,
+                /* slowDown= */ true,
+                LauncherInstrumentation.GestureScope.INSIDE);
     }
 
     private int getStartDragThreshold() {
@@ -148,6 +167,4 @@
     }
 
     protected abstract void addExpectedEventsForLongClick();
-
-    protected abstract String getLongPressIndicator();
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
index 3f1be80..2033a42 100644
--- a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
+++ b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
@@ -16,11 +16,27 @@
 
 package com.android.launcher3.tapl;
 
+import static com.android.launcher3.testing.TestProtocol.REQUEST_DISABLE_MANUAL_TASKBAR_STASHING;
+import static com.android.launcher3.testing.TestProtocol.REQUEST_ENABLE_MANUAL_TASKBAR_STASHING;
+import static com.android.launcher3.testing.TestProtocol.REQUEST_STASHED_TASKBAR_HEIGHT;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.view.MotionEvent;
+
+import androidx.test.uiautomator.By;
+
+import com.android.launcher3.testing.TestProtocol;
+
 /**
  * Background state operations specific to when an app has been launched.
  */
 public final class LaunchedAppState extends Background {
 
+    // More drag steps than Launchables to give the window manager time to register the drag.
+    private static final int DEFAULT_DRAG_STEPS = 35;
+
     LaunchedAppState(LauncherInstrumentation launcher) {
         super(launcher);
     }
@@ -29,4 +45,126 @@
     protected LauncherInstrumentation.ContainerType getContainerType() {
         return LauncherInstrumentation.ContainerType.LAUNCHED_APP;
     }
+
+    /**
+     * Returns the taskbar.
+     *
+     * The taskbar must already be visible when calling this method.
+     */
+    public Taskbar getTaskbar() {
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "want to get the taskbar")) {
+            mLauncher.waitForLauncherObject("taskbar_view");
+
+            return new Taskbar(mLauncher);
+        }
+    }
+
+    /**
+     * Returns the Taskbar in a visible state.
+     *
+     * The taskbar must already be hidden when calling this method.
+     */
+    public Taskbar showTaskbar() {
+        mLauncher.getTestInfo(REQUEST_ENABLE_MANUAL_TASKBAR_STASHING);
+
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+             LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+                     "want to show the taskbar")) {
+            mLauncher.waitUntilLauncherObjectGone("taskbar_view");
+
+            final long downTime = SystemClock.uptimeMillis();
+            final int unstashTargetY = mLauncher.getRealDisplaySize().y
+                    - (mLauncher.getTestInfo(REQUEST_STASHED_TASKBAR_HEIGHT)
+                            .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD) / 2);
+            final Point unstashTarget = new Point(
+                    mLauncher.getRealDisplaySize().x / 2, unstashTargetY);
+
+            mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, unstashTarget,
+                    LauncherInstrumentation.GestureScope.OUTSIDE_WITH_PILFER);
+            LauncherInstrumentation.log("showTaskbar: sent down");
+
+            try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer("pressed down")) {
+                mLauncher.waitForLauncherObject("taskbar_view");
+                mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_UP, unstashTarget,
+                        LauncherInstrumentation.GestureScope.OUTSIDE_WITH_PILFER);
+
+                return new Taskbar(mLauncher);
+            }
+        } finally {
+            mLauncher.getTestInfo(REQUEST_DISABLE_MANUAL_TASKBAR_STASHING);
+        }
+    }
+
+    static void dragToSplitscreen(
+            LauncherInstrumentation launcher, Launchable launchable, String expectedNewPackageName,
+            String expectedExistingPackageName) {
+        try (LauncherInstrumentation.Closable c1 = launcher.addContextLayer(
+                "want to drag taskbar item to splitscreen")) {
+            final Point displaySize = launcher.getRealDisplaySize();
+            final Point endPoint = new Point(displaySize.x / 4, 3 * displaySize.y / 4);
+            final long downTime = SystemClock.uptimeMillis();
+            // Use mObject before starting drag since the system drag and drop moves the original
+            // view.
+            Point itemVisibleCenter = launchable.mObject.getVisibleCenter();
+            Rect itemVisibleBounds = launcher.getVisibleBounds(launchable.mObject);
+            String itemLabel = launchable.mObject.getText();
+
+            Point dragStart = launchable.startDrag(
+                    downTime,
+                    launchable::addExpectedEventsForLongClick,
+                    /* runToSpringLoadedState= */ false);
+
+            try (LauncherInstrumentation.Closable c2 = launcher.addContextLayer(
+                    "started item drag")) {
+                launcher.movePointer(
+                        dragStart,
+                        endPoint,
+                        DEFAULT_DRAG_STEPS,
+                        /* isDecelerating= */ true,
+                        downTime,
+                        SystemClock.uptimeMillis(),
+                        /* slowDown= */ false,
+                        LauncherInstrumentation.GestureScope.INSIDE);
+
+                try (LauncherInstrumentation.Closable c3 = launcher.addContextLayer(
+                        "moved pointer to drop point")) {
+                    dropDraggedItem(
+                            launcher,
+                            launchable,
+                            expectedNewPackageName,
+                            endPoint, downTime,
+                            itemVisibleCenter,
+                            itemVisibleBounds,
+                            itemLabel,
+                            expectedExistingPackageName);
+                }
+            }
+        }
+    }
+
+    private static void dropDraggedItem(
+            LauncherInstrumentation launcher, Launchable launchable, String expectedNewPackageName,
+            Point endPoint, long downTime, Point itemVisibleCenter, Rect itemVisibleBounds,
+            String itemLabel, String expectedExistingPackageName) {
+        LauncherInstrumentation.log("SplitscreenDragSource.dragToSplitscreen before drop "
+                + itemVisibleCenter + " in " + itemVisibleBounds);
+
+        launchable.executeAndWaitForWindowChange(() -> {
+            launcher.sendPointer(
+                    downTime,
+                    SystemClock.uptimeMillis(),
+                    MotionEvent.ACTION_UP,
+                    endPoint,
+                    LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE_WITHOUT_PILFER);
+            LauncherInstrumentation.log("SplitscreenDragSource.dragToSplitscreen: after "
+                    + "drop");
+        }, itemLabel, "dropping taskbar item");
+
+        try (LauncherInstrumentation.Closable c = launcher.addContextLayer("dropped item")) {
+            launchable.assertAppLaunched(itemLabel, By.pkg(expectedNewPackageName));
+            launcher.checkPackagesVisible(
+                    new String[] {expectedNewPackageName, expectedExistingPackageName});
+        }
+    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index bbfbc55..1e3b078 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -84,6 +84,7 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Optional;
+import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.function.Consumer;
@@ -123,7 +124,8 @@
     // Types for launcher containers that the user is interacting with. "Background" is a
     // pseudo-container corresponding to inactive launcher covered by another app.
     public enum ContainerType {
-        WORKSPACE, HOME_ALL_APPS, OVERVIEW, WIDGETS, FALLBACK_OVERVIEW, LAUNCHED_APP
+        WORKSPACE, HOME_ALL_APPS, OVERVIEW, WIDGETS, FALLBACK_OVERVIEW, LAUNCHED_APP,
+        TASKBAR_ALL_APPS
     }
 
     public enum NavigationModel {ZERO_BUTTON, THREE_BUTTON}
@@ -167,6 +169,7 @@
     private static final String OVERVIEW_RES_ID = "overview_panel";
     private static final String WIDGETS_RES_ID = "primary_widgets_list_view";
     private static final String CONTEXT_MENU_RES_ID = "popup_container";
+    private static final String TASKBAR_RES_ID = "taskbar_view";
     public static final int WAIT_TIME_MS = 60000;
     private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
     private static final String ANDROID_PACKAGE = "android";
@@ -500,6 +503,16 @@
         }
     }
 
+    void checkPackagesVisible(String[] expectedVisiblePackages) {
+        Set<String> actualVisiblePackages =
+                getVisiblePackagesStream().collect(Collectors.toSet());
+
+        for (String expectedVisible : expectedVisiblePackages) {
+            assertTrue("Expected package not visible: " + expectedVisible,
+                    actualVisiblePackages.contains(expectedVisible));
+        }
+    }
+
     private String getVisiblePackages() {
         final String apps = getVisiblePackagesStream().collect(Collectors.joining(", "));
         return !apps.isEmpty()
@@ -722,27 +735,41 @@
                     waitUntilLauncherObjectGone(APPS_RES_ID);
                     waitUntilLauncherObjectGone(OVERVIEW_RES_ID);
                     waitUntilLauncherObjectGone(WIDGETS_RES_ID);
+                    waitUntilLauncherObjectGone(TASKBAR_RES_ID);
+
                     return waitForLauncherObject(WORKSPACE_RES_ID);
                 }
                 case WIDGETS: {
                     waitUntilLauncherObjectGone(WORKSPACE_RES_ID);
                     waitUntilLauncherObjectGone(APPS_RES_ID);
                     waitUntilLauncherObjectGone(OVERVIEW_RES_ID);
+                    waitUntilLauncherObjectGone(TASKBAR_RES_ID);
+
                     return waitForLauncherObject(WIDGETS_RES_ID);
                 }
+                case TASKBAR_ALL_APPS:
                 case HOME_ALL_APPS: {
                     waitUntilLauncherObjectGone(WORKSPACE_RES_ID);
                     waitUntilLauncherObjectGone(OVERVIEW_RES_ID);
                     waitUntilLauncherObjectGone(WIDGETS_RES_ID);
+                    waitUntilLauncherObjectGone(TASKBAR_RES_ID);
+
                     return waitForLauncherObject(APPS_RES_ID);
                 }
                 case OVERVIEW: {
                     waitUntilLauncherObjectGone(APPS_RES_ID);
                     waitUntilLauncherObjectGone(WORKSPACE_RES_ID);
                     waitUntilLauncherObjectGone(WIDGETS_RES_ID);
+                    waitUntilLauncherObjectGone(TASKBAR_RES_ID);
+
                     return waitForLauncherObject(OVERVIEW_RES_ID);
                 }
                 case FALLBACK_OVERVIEW: {
+                    waitUntilLauncherObjectGone(APPS_RES_ID);
+                    waitUntilLauncherObjectGone(WORKSPACE_RES_ID);
+                    waitUntilLauncherObjectGone(WIDGETS_RES_ID);
+                    waitUntilLauncherObjectGone(TASKBAR_RES_ID);
+
                     return waitForFallbackLauncherObject(OVERVIEW_RES_ID);
                 }
                 case LAUNCHED_APP: {
@@ -750,6 +777,12 @@
                     waitUntilLauncherObjectGone(APPS_RES_ID);
                     waitUntilLauncherObjectGone(OVERVIEW_RES_ID);
                     waitUntilLauncherObjectGone(WIDGETS_RES_ID);
+
+                    if (isTablet() && !isFallbackOverview()) {
+                        waitForLauncherObject(TASKBAR_RES_ID);
+                    } else {
+                        waitUntilLauncherObjectGone(TASKBAR_RES_ID);
+                    }
                     return null;
                 }
                 default:
@@ -863,8 +896,13 @@
                 setForcePauseTimeout(FORCE_PAUSE_TIMEOUT_MS);
 
                 final Point displaySize = getRealDisplaySize();
+                // The swipe up to home gesture starts from inside the launcher when the user is
+                // already home. Otherwise, the gesture can start inside the launcher process if the
+                // taskbar is visible.
                 boolean gestureStartFromLauncher = isTablet()
-                        ? !isLauncher3() || hasLauncherObject(WORKSPACE_RES_ID)
+                        ? !isLauncher3()
+                        || hasLauncherObject(WORKSPACE_RES_ID)
+                        || hasLauncherObject(TASKBAR_RES_ID)
                         : isLauncherVisible();
 
                 // CLose floating views before going back to home.
@@ -1301,9 +1339,7 @@
     }
 
     void scrollToLastVisibleRow(
-            UiObject2 container,
-            Collection<UiObject2> items,
-            int topPaddingInContainer) {
+            UiObject2 container, Collection<UiObject2> items, int topPaddingInContainer) {
         final UiObject2 lowestItem = Collections.max(items, (i1, i2) ->
                 Integer.compare(getVisibleBounds(i1).top, getVisibleBounds(i2).top));
 
@@ -1326,8 +1362,8 @@
                         containerRect.height() - distance - bottomGestureMarginInContainer,
                         0,
                         bottomGestureMarginInContainer),
-                10,
-                true);
+                /* steps= */ 10,
+                /* slowDown= */ true);
     }
 
     void scrollLeftByDistance(UiObject2 container, int distance) {
@@ -1650,6 +1686,29 @@
         getTestInfo(TestProtocol.REQUEST_CLEAR_DATA);
     }
 
+    /**
+     * Reloads the workspace with a test layout that includes the Test Activity app icon on the
+     * hotseat.
+     */
+    public void useTestWorkspaceLayoutOnReload() {
+        getTestInfo(TestProtocol.REQUEST_USE_TEST_WORKSPACE_LAYOUT);
+    }
+
+    /** Reloads the workspace with the default layout defined by the user's grid size selection. */
+    public void useDefaultWorkspaceLayoutOnReload() {
+        getTestInfo(TestProtocol.REQUEST_USE_DEFAULT_WORKSPACE_LAYOUT);
+    }
+
+    /** Shows the taskbar if it is hidden, otherwise does nothing. */
+    public void showTaskbarIfHidden() {
+        getTestInfo(TestProtocol.REQUEST_UNSTASH_TASKBAR_IF_STASHED);
+    }
+
+    public List<String> getHotseatIconNames() {
+        return getTestInfo(TestProtocol.REQUEST_HOTSEAT_ICON_NAMES)
+                .getStringArrayList(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+    }
+
     private String[] getActivities() {
         return getTestInfo(TestProtocol.REQUEST_GET_ACTIVITIES)
                 .getStringArray(TestProtocol.TEST_INFO_RESPONSE_FIELD);
diff --git a/tests/tapl/com/android/launcher3/tapl/SplitscreenDragSource.java b/tests/tapl/com/android/launcher3/tapl/SplitscreenDragSource.java
new file mode 100644
index 0000000..ce1c3c0
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/SplitscreenDragSource.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 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.tapl;
+
+/** Launchable that can serve as a source for dragging and dropping to splitscreen. */
+interface SplitscreenDragSource {
+
+    /**
+     * Drags this app icon to the left (landscape) or bottom (portrait) of the screen, launching it
+     * in splitscreen.
+     *
+     * @param expectedNewPackageName package name of the app being dragged
+     * @param expectedExistingPackageName package name of the already-launched app
+     */
+    default void dragToSplitscreen(
+            String expectedNewPackageName, String expectedExistingPackageName) {
+        Launchable launchable = getLaunchable();
+        LauncherInstrumentation launcher = launchable.mLauncher;
+        try (LauncherInstrumentation.Closable e = launcher.eventsCheck()) {
+            LaunchedAppState.dragToSplitscreen(
+                    launcher, launchable, expectedNewPackageName, expectedExistingPackageName);
+        }
+    }
+
+    Launchable getLaunchable();
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/Taskbar.java b/tests/tapl/com/android/launcher3/tapl/Taskbar.java
new file mode 100644
index 0000000..b5a08c3
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/Taskbar.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2022 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.tapl;
+
+import static com.android.launcher3.testing.TestProtocol.REQUEST_DISABLE_MANUAL_TASKBAR_STASHING;
+import static com.android.launcher3.testing.TestProtocol.REQUEST_ENABLE_MANUAL_TASKBAR_STASHING;
+
+import android.graphics.Point;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.view.MotionEvent;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.UiObject2;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Operations on the Taskbar from LaunchedApp.
+ */
+public final class Taskbar {
+
+    private final LauncherInstrumentation mLauncher;
+
+    Taskbar(LauncherInstrumentation launcher) {
+        mLauncher = launcher;
+    }
+
+    /**
+     * Returns an app icon with the given name. This fails if the icon is not found.
+     */
+    @NonNull
+    public TaskbarAppIcon getAppIcon(String appName) {
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "want to get a taskbar icon")) {
+            return new TaskbarAppIcon(mLauncher, mLauncher.waitForObjectInContainer(
+                    mLauncher.waitForLauncherObject("taskbar_view"),
+                    AppIcon.getAppIconSelector(appName, mLauncher)));
+        }
+    }
+
+    /**
+     * Hides this taskbar.
+     *
+     * The taskbar must already be visible when calling this method.
+     */
+    public void hide() {
+        mLauncher.getTestInfo(REQUEST_ENABLE_MANUAL_TASKBAR_STASHING);
+
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "want to hide the taskbar");
+             LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+            mLauncher.waitForLauncherObject("taskbar_view");
+
+            final long downTime = SystemClock.uptimeMillis();
+            Point stashTarget = new Point(
+                    mLauncher.getRealDisplaySize().x - 1, mLauncher.getRealDisplaySize().y - 1);
+
+            mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, stashTarget,
+                    LauncherInstrumentation.GestureScope.INSIDE);
+            LauncherInstrumentation.log("hideTaskbar: sent down");
+
+            try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer("pressed down")) {
+                mLauncher.waitUntilLauncherObjectGone("taskbar_view");
+                mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_UP, stashTarget,
+                        LauncherInstrumentation.GestureScope.INSIDE);
+            }
+        } finally {
+            mLauncher.getTestInfo(REQUEST_DISABLE_MANUAL_TASKBAR_STASHING);
+        }
+    }
+
+    /**
+     * Opens the Taskbar all apps page.
+     */
+    public AllAppsFromTaskbar openAllApps() {
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "want to open taskbar all apps");
+             LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+
+            mLauncher.clickLauncherObject(mLauncher.waitForObjectInContainer(
+                    mLauncher.waitForLauncherObject("taskbar_view"), getAllAppsButtonSelector()));
+
+            return new AllAppsFromTaskbar(mLauncher);
+        }
+    }
+
+    /** Returns a list of app icon names on the Taskbar */
+    public List<String> getIconNames() {
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "want to get all taskbar icons")) {
+            return mLauncher.waitForObjectsInContainer(
+                    mLauncher.waitForLauncherObject("taskbar_view"),
+                    AppIcon.getAnyAppIconSelector())
+                    .stream()
+                    .map(UiObject2::getText)
+                    .filter(text -> !TextUtils.isEmpty(text)) // Filter out the all apps button
+                    .collect(Collectors.toList());
+        }
+    }
+
+    private static BySelector getAllAppsButtonSelector() {
+        // Look for an icon with no text
+        return By.clazz(TextView.class).text("");
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/TaskbarAppIcon.java b/tests/tapl/com/android/launcher3/tapl/TaskbarAppIcon.java
new file mode 100644
index 0000000..099acd4
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/TaskbarAppIcon.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 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.tapl;
+
+import androidx.test.uiautomator.UiObject2;
+
+import java.util.regex.Pattern;
+
+/**
+ * App icon specifically on the Taskbar.
+ */
+public final class TaskbarAppIcon extends AppIcon implements SplitscreenDragSource {
+
+    private static final Pattern LONG_CLICK_EVENT = Pattern.compile("onTaskbarItemLongClick");
+
+    TaskbarAppIcon(LauncherInstrumentation launcher, UiObject2 icon) {
+        super(launcher, icon);
+    }
+
+    @Override
+    protected Pattern getLongClickEvent() {
+        return LONG_CLICK_EVENT;
+    }
+
+    @Override
+    public TaskbarAppIconMenu openDeepShortcutMenu() {
+        return (TaskbarAppIconMenu) super.openDeepShortcutMenu();
+    }
+
+    @Override
+    protected TaskbarAppIconMenu createMenu(UiObject2 menu) {
+        return new TaskbarAppIconMenu(mLauncher, menu);
+    }
+
+    @Override
+    public Launchable getLaunchable() {
+        return this;
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/TaskbarAppIconMenu.java b/tests/tapl/com/android/launcher3/tapl/TaskbarAppIconMenu.java
new file mode 100644
index 0000000..1f137c5
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/TaskbarAppIconMenu.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 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.tapl;
+
+import androidx.test.uiautomator.UiObject2;
+
+/**
+ * Context menu of a Taskbar app icon.
+ */
+public final class TaskbarAppIconMenu extends AppIconMenu {
+
+    TaskbarAppIconMenu(LauncherInstrumentation launcher, UiObject2 deepShortcutsContainer) {
+        super(launcher, deepShortcutsContainer);
+    }
+
+    @Override
+    public TaskbarAppIconMenuItem getMenuItem(String shortcutText) {
+        return (TaskbarAppIconMenuItem) super.getMenuItem(shortcutText);
+    }
+
+    @Override
+    protected TaskbarAppIconMenuItem createMenuItem(UiObject2 menuItem) {
+        return new TaskbarAppIconMenuItem(mLauncher, menuItem);
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/TaskbarAppIconMenuItem.java b/tests/tapl/com/android/launcher3/tapl/TaskbarAppIconMenuItem.java
new file mode 100644
index 0000000..69a8a08
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/TaskbarAppIconMenuItem.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 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.tapl;
+
+import androidx.test.uiautomator.UiObject2;
+
+import com.android.launcher3.testing.TestProtocol;
+
+import java.util.regex.Pattern;
+
+/**
+ * Menu item in a Taskbar app icon menu.
+ */
+public final class TaskbarAppIconMenuItem extends AppIconMenuItem implements SplitscreenDragSource {
+
+    private static final Pattern LONG_CLICK_EVENT = Pattern.compile("onTaskbarItemLongClick");
+
+    TaskbarAppIconMenuItem(
+            LauncherInstrumentation launcher, UiObject2 shortcut) {
+        super(launcher, shortcut);
+    }
+
+    @Override
+    protected void addExpectedEventsForLongClick() {
+        mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, LONG_CLICK_EVENT);
+    }
+
+    @Override
+    protected void waitForLongPressConfirmation() {
+        // On long-press, the popup container closes and the system drag-and-drop begins. This
+        // only leaves launcher views that were previously visible.
+        mLauncher.waitUntilLauncherObjectGone("popup_container");
+    }
+
+    @Override
+    protected String launchableType() {
+        return "taskbar app icon menu item";
+    }
+
+    @Override
+    public Launchable getLaunchable() {
+        return this;
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/Widget.java b/tests/tapl/com/android/launcher3/tapl/Widget.java
index 73e9830..2346249 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widget.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widget.java
@@ -39,8 +39,8 @@
     }
 
     @Override
-    protected String getLongPressIndicator() {
-        return "drop_target_bar";
+    protected void waitForLongPressConfirmation() {
+        mLauncher.waitForLauncherObject("drop_target_bar");
     }
 
     @Override
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 3bc5389..fee4490 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -205,7 +205,6 @@
                 mLauncher,
                 homeAppIcon,
                 new Point(targetX, mLauncher.getVisibleBounds(workspace).centerY()),
-                "popup_container",
                 false,
                 false,
                 () -> mLauncher.expectEvent(
@@ -244,11 +243,11 @@
              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                      "removing app icon from workspace")) {
             dragIconToWorkspace(
-                    mLauncher, homeAppIcon,
+                    mLauncher,
+                    homeAppIcon,
                     () -> getDropPointFromDropTargetBar(mLauncher, DELETE_TARGET_TEXT_ID),
-                    homeAppIcon.getLongPressIndicator(),
                     () -> mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, LONG_CLICK_EVENT),
-                    null);
+                    /* expectDropEvents= */ null);
 
             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
                     "dragged the app to the drop bar")) {
@@ -274,11 +273,11 @@
         try (LauncherInstrumentation.Closable c = launcher.addContextLayer(
                 "uninstalling app icon")) {
             dragIconToWorkspace(
-                    launcher, homeAppIcon,
+                    launcher,
+                    homeAppIcon,
                     () -> getDropPointFromDropTargetBar(launcher, UNINSTALL_TARGET_TEXT_ID),
-                    homeAppIcon.getLongPressIndicator(),
                     expectLongClickEvents,
-                    null);
+                    /* expectDropEvents= */null);
 
             launcher.waitUntilLauncherObjectGone(DROP_BAR_RES_ID);
 
@@ -345,15 +344,15 @@
     }
 
     static void dragIconToWorkspace(LauncherInstrumentation launcher, Launchable launchable,
-            Point dest, String longPressIndicator, boolean startsActivity, boolean isWidgetShortcut,
+            Point dest, boolean startsActivity, boolean isWidgetShortcut,
             Runnable expectLongClickEvents) {
         Runnable expectDropEvents = null;
         if (startsActivity || isWidgetShortcut) {
             expectDropEvents = () -> launcher.expectEvent(TestProtocol.SEQUENCE_MAIN,
                     LauncherInstrumentation.EVENT_START);
         }
-        dragIconToWorkspace(launcher, launchable, () -> dest, longPressIndicator,
-                expectLongClickEvents, expectDropEvents);
+        dragIconToWorkspace(
+                launcher, launchable, () -> dest, expectLongClickEvents, expectDropEvents);
     }
 
     /**
@@ -361,22 +360,27 @@
      * (There is no slow down time before drop event)
      * This function expects the launchable is inside the workspace and there is no drop event.
      */
-    static void dragIconToWorkspace(LauncherInstrumentation launcher, Launchable launchable,
-            Supplier<Point> destSupplier, String longPressIndicator) {
-        dragIconToWorkspace(launcher, launchable, destSupplier, longPressIndicator,
-                () -> launcher.expectEvent(TestProtocol.SEQUENCE_MAIN, LONG_CLICK_EVENT), null);
+    static void dragIconToWorkspace(
+            LauncherInstrumentation launcher, Launchable launchable, Supplier<Point> destSupplier) {
+        dragIconToWorkspace(
+                launcher,
+                launchable,
+                destSupplier,
+                () -> launcher.expectEvent(TestProtocol.SEQUENCE_MAIN, LONG_CLICK_EVENT),
+                /* expectDropEvents= */ null);
     }
 
     static void dragIconToWorkspace(
-            LauncherInstrumentation launcher, Launchable launchable, Supplier<Point> dest,
-            String longPressIndicator, Runnable expectLongClickEvents,
+            LauncherInstrumentation launcher,
+            Launchable launchable,
+            Supplier<Point> dest,
+            Runnable expectLongClickEvents,
             @Nullable Runnable expectDropEvents) {
         try (LauncherInstrumentation.Closable ignored = launcher.addContextLayer(
                 "want to drag icon to workspace")) {
             final long downTime = SystemClock.uptimeMillis();
             Point dragStart = launchable.startDrag(
                     downTime,
-                    longPressIndicator,
                     expectLongClickEvents,
                     /* runToSpringLoadedState= */ true);
             Point targetDest = dest.get();
diff --git a/tests/tapl/com/android/launcher3/tapl/WorkspaceDragSource.java b/tests/tapl/com/android/launcher3/tapl/WorkspaceDragSource.java
index 93c2213..d8d4420 100644
--- a/tests/tapl/com/android/launcher3/tapl/WorkspaceDragSource.java
+++ b/tests/tapl/com/android/launcher3/tapl/WorkspaceDragSource.java
@@ -41,7 +41,6 @@
                                     ? launchableCenter.x - width / 2
                                     : launchableCenter.x + width / 2,
                             displaySize.y / 2),
-                    launchable.getLongPressIndicator(),
                     startsActivity,
                     isWidgetShortcut,
                     launchable::addExpectedEventsForLongClick);