Merge "Fix two panel tapl test dragging issue"
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index 59ade49..099915a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -221,13 +221,9 @@
      * @return true if the event is over the hotseat
      */
     static boolean isTouchOverHotseat(Launcher launcher, MotionEvent ev) {
-        return (ev.getY() >= getHotseatTop(launcher));
-    }
-
-    public static int getHotseatTop(Launcher launcher) {
         DeviceProfile dp = launcher.getDeviceProfile();
         int hotseatHeight = dp.hotseatBarSizePx + dp.getInsets().bottom;
-        return launcher.getDragLayer().getHeight() - hotseatHeight;
+        return (ev.getY() >= (launcher.getDragLayer().getHeight() - hotseatHeight));
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 4c570f1..ef81449 100644
--- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -7,11 +7,9 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.LauncherState;
 import com.android.launcher3.testing.TestInformationHandler;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
 import com.android.quickstep.util.LayoutUtils;
 
 public class QuickstepTestInformationHandler extends TestInformationHandler {
@@ -26,15 +24,6 @@
     public Bundle call(String method, String arg, @Nullable Bundle extras) {
         final Bundle response = new Bundle();
         switch (method) {
-            case TestProtocol.REQUEST_ALL_APPS_TO_OVERVIEW_SWIPE_HEIGHT: {
-                return getLauncherUIProperty(Bundle::putInt, l -> {
-                    final float progress = LauncherState.OVERVIEW.getVerticalProgress(l)
-                            - LauncherState.ALL_APPS.getVerticalProgress(l);
-                    final float distance = l.getAllAppsController().getShiftRange() * progress;
-                    return (int) distance;
-                });
-            }
-
             case TestProtocol.REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT: {
                 final float swipeHeight =
                         LayoutUtils.getDefaultSwipeHeight(mContext, mDeviceProfile);
@@ -50,11 +39,6 @@
                 return response;
             }
 
-            case TestProtocol.REQUEST_HOTSEAT_TOP: {
-                return getLauncherUIProperty(
-                        Bundle::putInt, PortraitStatesTouchController::getHotseatTop);
-            }
-
             case TestProtocol.REQUEST_GET_FOCUSED_TASK_HEIGHT_FOR_TABLET: {
                 if (!mDeviceProfile.isTablet) {
                     return null;
diff --git a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
index 49e4bd3..307c51f 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
@@ -192,13 +192,12 @@
     }
 
     public void addAnimation(PendingAnimation animation, RectF startingBounds, Rect endBounds,
-            View viewToCover, boolean fadeWithThumbnail) {
+            boolean fadeWithThumbnail) {
         final BaseDragLayer dragLayer = mActivity.getDragLayer();
         int[] dragLayerBounds = new int[2];
         dragLayer.getLocationOnScreen(dragLayerBounds);
         SplitOverlayProperties prop = new SplitOverlayProperties(endBounds,
-                startingBounds, viewToCover, dragLayerBounds[0],
-                dragLayerBounds[1]);
+                startingBounds, dragLayerBounds[0], dragLayerBounds[1]);
 
         ValueAnimator transitionAnimator = ValueAnimator.ofFloat(0, 1);
         animation.add(transitionAnimator);
@@ -221,10 +220,10 @@
                     initialWindowRadius, 0, animDuration, LINEAR);
             final FloatProp mDx = new FloatProp(0, prop.dX, 0, animDuration, LINEAR);
             final FloatProp mDy = new FloatProp(0, prop.dY, 0, animDuration, LINEAR);
-            final FloatProp mTaskViewScaleX = new FloatProp(prop.initialTaskViewScaleX,
-                    prop.finalTaskViewScaleX, 0, animDuration, LINEAR);
-            final FloatProp mTaskViewScaleY = new FloatProp(prop.initialTaskViewScaleY,
-                    prop.finalTaskViewScaleY, 0, animDuration, LINEAR);
+            final FloatProp mTaskViewScaleX = new FloatProp(1f, prop.finalTaskViewScaleX, 0,
+                    animDuration, LINEAR);
+            final FloatProp mTaskViewScaleY = new FloatProp(1f, prop.finalTaskViewScaleY, 0,
+                    animDuration, LINEAR);
             @Override
             public void onUpdate(float percent, boolean initOnly) {
                 // Calculate the icon position.
@@ -241,24 +240,20 @@
 
     private static class SplitOverlayProperties {
 
-        private final float initialTaskViewScaleX;
-        private final float initialTaskViewScaleY;
         private final float finalTaskViewScaleX;
         private final float finalTaskViewScaleY;
         private final float dX;
         private final float dY;
 
-        SplitOverlayProperties(Rect endBounds, RectF startTaskViewBounds, View view,
+        SplitOverlayProperties(Rect endBounds, RectF startTaskViewBounds,
                 int dragLayerLeft, int dragLayerTop) {
             float maxScaleX = endBounds.width() / startTaskViewBounds.width();
             float maxScaleY = endBounds.height() / startTaskViewBounds.height();
 
-            initialTaskViewScaleX = view.getScaleX();
-            initialTaskViewScaleY = view.getScaleY();
             finalTaskViewScaleX = maxScaleX;
             finalTaskViewScaleY = maxScaleY;
 
-            // Animate the app icon to the center of the window bounds in screen coordinates.
+            // Animate to the center of the window bounds in screen coordinates.
             float centerX = endBounds.centerX() - dragLayerLeft;
             float centerY = endBounds.centerY() - dragLayerTop;
 
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index b9ca6ca..a042bff 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -2736,7 +2736,7 @@
                     ));
             mFirstFloatingTaskView.setAlpha(1);
             mFirstFloatingTaskView.addAnimation(anim, startingTaskRect,
-                    mTempRect, mSplitHiddenTaskView, true /*fadeWithThumbnail*/);
+                    mTempRect, true /*fadeWithThumbnail*/);
         } else {
             mSplitSelectSource.view.setVisibility(INVISIBLE);
             mFirstFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
@@ -2744,7 +2744,7 @@
                     mSplitSelectSource.drawable, startingTaskRect, null /*additionalOffsetter*/);
             mFirstFloatingTaskView.setAlpha(1);
             mFirstFloatingTaskView.addAnimation(anim, startingTaskRect,
-                    mTempRect, mSplitSelectSource.view, true /*fadeWithThumbnail*/);
+                    mTempRect, true /*fadeWithThumbnail*/);
         }
         anim.addEndListener(success -> {
             if (success) {
@@ -4028,7 +4028,7 @@
 
         mFirstFloatingTaskView.getBoundsOnScreen(firstTaskStartingBounds);
         mFirstFloatingTaskView.addAnimation(pendingAnimation,
-                new RectF(firstTaskStartingBounds), firstTaskEndingBounds, mFirstFloatingTaskView,
+                new RectF(firstTaskStartingBounds), firstTaskEndingBounds,
                 false /*fadeWithThumbnail*/);
 
         mSecondFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
@@ -4040,8 +4040,7 @@
                 ));
         mSecondFloatingTaskView.setAlpha(1);
         mSecondFloatingTaskView.addAnimation(pendingAnimation, secondTaskStartingBounds,
-                secondTaskEndingBounds, taskView.getThumbnail(),
-                true /*fadeWithThumbnail*/);
+                secondTaskEndingBounds, true /*fadeWithThumbnail*/);
         pendingAnimation.addEndListener(aBoolean ->
                 mSplitSelectStateController.setSecondTaskId(taskView.getTask().key.id,
                 aBoolean1 -> RecentsView.this.resetFromSplitSelectionState()));
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index cdb8082..488789b 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -464,11 +464,15 @@
         return getItemInfo(mTask);
     }
 
-    protected WorkspaceItemInfo getItemInfo(Task task) {
-        ComponentKey componentKey = TaskUtils.getLaunchComponentKeyForTask(task.key);
+    protected WorkspaceItemInfo getItemInfo(@Nullable Task task) {
         WorkspaceItemInfo stubInfo = new WorkspaceItemInfo();
         stubInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_TASK;
         stubInfo.container = LauncherSettings.Favorites.CONTAINER_TASKSWITCHER;
+        if (task == null) {
+            return stubInfo;
+        }
+
+        ComponentKey componentKey = TaskUtils.getLaunchComponentKeyForTask(task.key);
         stubInfo.user = componentKey.user;
         stubInfo.intent = new Intent().setComponent(componentKey.componentName);
         stubInfo.title = task.title;
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index cba4833..4529217 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -165,7 +165,7 @@
         assertTrue("Fallback Launcher not visible", mDevice.wait(Until.hasObject(By.pkg(
                 mOtherLauncherActivity.packageName)), WAIT_TIME_MS));
 
-        mLauncher.getBackground().switchToOverview();
+        mLauncher.getLaunchedAppState().switchToOverview();
     }
 
     // b/143488140
@@ -174,7 +174,7 @@
     public void goToOverviewFromApp() {
         startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
 
-        mLauncher.getBackground().switchToOverview();
+        mLauncher.getLaunchedAppState().switchToOverview();
     }
 
     protected void executeOnRecents(Consumer<RecentsActivity> f) {
@@ -200,7 +200,7 @@
 
     private BaseOverview pressHomeAndGoToOverview() {
         mDevice.pressHome();
-        return mLauncher.getBackground().switchToOverview();
+        return mLauncher.getLaunchedAppState().switchToOverview();
     }
 
     // b/143488140
@@ -213,7 +213,7 @@
         Wait.atMost("Expected three apps in the task list",
                 () -> mLauncher.getRecentTasks().size() >= 3, DEFAULT_ACTIVITY_TIMEOUT, mLauncher);
 
-        BaseOverview overview = mLauncher.getBackground().switchToOverview();
+        BaseOverview overview = mLauncher.getLaunchedAppState().switchToOverview();
         executeOnRecents(recents -> {
             assertTrue("Don't have at least 3 tasks", getTaskCount(recents) >= 3);
         });
diff --git a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
index 75d057e..5351963 100644
--- a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
+++ b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
@@ -78,7 +78,7 @@
             closeLauncherActivity();
 
             // The test action.
-            mLauncher.getBackground().switchToOverview();
+            mLauncher.getLaunchedAppState().switchToOverview();
         }
         closeLauncherActivity();
         mLauncher.pressHome();
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index e3198a8..396cb1b 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -32,8 +32,8 @@
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.tapl.AllApps;
-import com.android.launcher3.tapl.Background;
+import com.android.launcher3.tapl.HomeAllApps;
+import com.android.launcher3.tapl.LaunchedAppState;
 import com.android.launcher3.tapl.LauncherInstrumentation.NavigationModel;
 import com.android.launcher3.tapl.Overview;
 import com.android.launcher3.tapl.OverviewActions;
@@ -84,7 +84,7 @@
         executeOnLauncher(launcher -> assertTrue(
                 "Launcher activity is the top activity; expecting another activity to be the top "
                         + "one",
-                isInBackground(launcher)));
+                isInLaunchedApp(launcher)));
     }
 
     @Test
@@ -136,7 +136,7 @@
         executeOnLauncher(launcher -> assertTrue(
                 "Launcher activity is the top activity; expecting another activity to be the top "
                         + "one",
-                isInBackground(launcher)));
+                isInLaunchedApp(launcher)));
 
         // Test dismissing a task.
         overview = mLauncher.pressHome().switchToOverview();
@@ -210,21 +210,22 @@
     @PortraitLandscape
     public void testBackground() throws Exception {
         startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
-        final Background background = getAndAssertBackground();
+        final LaunchedAppState launchedAppState = getAndAssertLaunchedApp();
 
-        assertNotNull("Background.switchToOverview() returned null", background.switchToOverview());
+        assertNotNull("Background.switchToOverview() returned null",
+                launchedAppState.switchToOverview());
         assertTrue("Launcher internal state didn't switch to Overview",
                 isInState(() -> LauncherState.OVERVIEW));
     }
 
-    private Background getAndAssertBackground() {
-        final Background background = mLauncher.getBackground();
-        assertNotNull("Launcher.getBackground() returned null", background);
+    private LaunchedAppState getAndAssertLaunchedApp() {
+        final LaunchedAppState launchedAppState = mLauncher.getLaunchedAppState();
+        assertNotNull("Launcher.getLaunchedApp() returned null", launchedAppState);
         executeOnLauncher(launcher -> assertTrue(
                 "Launcher activity is the top activity; expecting another activity to be the top "
                         + "one",
-                isInBackground(launcher)));
-        return background;
+                isInLaunchedApp(launcher)));
+        return launchedAppState;
     }
 
     @Test
@@ -253,13 +254,13 @@
         startTestActivity(3);
         startTestActivity(4);
 
-        Background background = getAndAssertBackground();
-        background.quickSwitchToPreviousApp();
+        LaunchedAppState launchedAppState = getAndAssertLaunchedApp();
+        launchedAppState.quickSwitchToPreviousApp();
         assertTrue("The first app we should have quick switched to is not running",
                 isTestActivityRunning(3));
 
-        background = getAndAssertBackground();
-        background.quickSwitchToPreviousApp();
+        launchedAppState = getAndAssertLaunchedApp();
+        launchedAppState.quickSwitchToPreviousApp();
         if (mLauncher.getNavigationModel() == NavigationModel.THREE_BUTTON) {
             // 3-button mode toggles between 2 apps, rather than going back further.
             assertTrue("Second quick switch should have returned to the first app.",
@@ -268,13 +269,13 @@
             assertTrue("The second app we should have quick switched to is not running",
                     isTestActivityRunning(2));
         }
-        background = getAndAssertBackground();
-        background.quickSwitchToPreviousAppSwipeLeft();
+        launchedAppState = getAndAssertLaunchedApp();
+        launchedAppState.quickSwitchToPreviousAppSwipeLeft();
         assertTrue("The 2nd app we should have quick switched to is not running",
                 isTestActivityRunning(3));
 
-        background = getAndAssertBackground();
-        background.switchToOverview();
+        launchedAppState = getAndAssertLaunchedApp();
+        launchedAppState.switchToOverview();
     }
 
     private boolean isTestActivityRunning(int activityNumber) {
@@ -291,7 +292,7 @@
         mLauncher.pressHome().quickSwitchToPreviousApp();
         assertTrue("The most recent task is not running after quick switching from home",
                 isTestActivityRunning(2));
-        getAndAssertBackground();
+        getAndAssertLaunchedApp();
     }
 
     // TODO(b/204830798): test with all navigation modes(add @NavigationModeSwitch annotation)
@@ -306,7 +307,7 @@
         mLauncher.getWorkspace();
         waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
 
-        AllApps allApps = mLauncher.getWorkspace().switchToAllApps();
+        HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
         allApps.freeze();
         try {
             allApps.getAppIcon(APP_NAME).dragToWorkspace(false, false);
diff --git a/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java b/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java
index 0f5a1c8..661beda 100644
--- a/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java
+++ b/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java
@@ -51,7 +51,7 @@
 
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
-import com.android.launcher3.tapl.Background;
+import com.android.launcher3.tapl.LaunchedAppState;
 import com.android.launcher3.testcomponent.ListViewService;
 import com.android.launcher3.testcomponent.ListViewService.SimpleViewsFactory;
 import com.android.launcher3.testcomponent.TestCommandReceiver;
@@ -119,13 +119,13 @@
         try {
             // Go to overview once so that all views are initialized and cached
             startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
-            mLauncher.getBackground().switchToOverview().dismissAllTasks();
+            mLauncher.getLaunchedAppState().switchToOverview().dismissAllTasks();
 
             // Track view creations
             mInitTracker.startTracking();
 
             startTestActivity(2);
-            mLauncher.getBackground().switchToOverview();
+            mLauncher.getLaunchedAppState().switchToOverview();
 
             assertEquals("Views inflated during swipe up", 0, mInitTracker.viewInitCount);
         } finally {
@@ -205,18 +205,18 @@
 
             // Go to overview once so that all views are initialized and cached
             startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
-            mLauncher.getBackground().switchToOverview().dismissAllTasks();
+            mLauncher.getLaunchedAppState().switchToOverview().dismissAllTasks();
 
             // Track view creations
             mInitTracker.startTracking();
 
             startTestActivity(2);
-            Background background = mLauncher.getBackground();
+            LaunchedAppState launchedAppState = mLauncher.getLaunchedAppState();
 
             // Update widget
             updateBeforeSwipeUp.accept(widgetId);
 
-            background.switchToOverview();
+            launchedAppState.switchToOverview();
             assertEquals("Views inflated during swipe up", 0, mInitTracker.viewInitCount);
 
             // Widget is updated when going home
diff --git a/res/values-v31/styles.xml b/res/values-v31/styles.xml
index 0d2fce0..e42d0a3 100644
--- a/res/values-v31/styles.xml
+++ b/res/values-v31/styles.xml
@@ -87,6 +87,7 @@
     <style name="HomeSettings.CollapsedToolbarTitle"
             parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
         <item name="android:fontFamily">google-sans</item>
+        <item name="android:textSize">20sp</item>
     </style>
 
     <style name="HomeSettings.ExpandedToolbarTitle" parent="HomeSettings.CollapsedToolbarTitle">
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index cbc21eb..9bc3d15 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -671,14 +671,15 @@
     /**
      * Returns the full drawable for info without any flattening or pre-processing.
      *
-     * @param outObj this is set to the internal data associated with {@param info},
+     * @param shouldThemeIcon If true, will theme icons when applicable
+     * @param outObj this is set to the internal data associated with {@code info},
      *               eg {@link LauncherActivityInfo} or {@link ShortcutInfo}.
      */
     @TargetApi(Build.VERSION_CODES.TIRAMISU)
     public static Drawable getFullDrawable(Context context, ItemInfo info, int width, int height,
-            Object[] outObj) {
+            boolean shouldThemeIcon, Object[] outObj) {
         Drawable icon = loadFullDrawableWithoutTheme(context, info, width, height, outObj);
-        if (ATLEAST_T && icon instanceof AdaptiveIconDrawable) {
+        if (ATLEAST_T && icon instanceof AdaptiveIconDrawable && shouldThemeIcon) {
             AdaptiveIconDrawable aid = (AdaptiveIconDrawable) icon.mutate();
             Drawable mono = aid.getMonochrome();
             if (mono != null && Themes.isThemedIconEnabled(context)) {
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 4588a04..8bfd774 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -216,7 +216,8 @@
             Object[] outObj = new Object[1];
             int w = mWidth;
             int h = mHeight;
-            Drawable dr = Utilities.getFullDrawable(mActivity, info, w, h, outObj);
+            Drawable dr = Utilities.getFullDrawable(mActivity, info, w, h,
+                    true /* shouldThemeIcon */, outObj);
 
             if (dr instanceof AdaptiveIconDrawable) {
                 int blurMargin = (int) mActivity.getResources()
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index f512ced..3129e2a 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -350,41 +350,101 @@
                                     iconRequest.itemInfo.getTargetComponent()));
 
             Trace.beginSection("loadIconSubsectionInBulk");
-            try (Cursor c = createBulkQueryCursor(
-                    filteredList,
-                    /* user = */ sectionKey.first,
-                    /* useLowResIcons = */ sectionKey.second)) {
-                int componentNameColumnIndex = c.getColumnIndexOrThrow(IconDB.COLUMN_COMPONENT);
-                while (c.moveToNext()) {
-                    ComponentName cn = ComponentName.unflattenFromString(
-                            c.getString(componentNameColumnIndex));
-                    List<IconRequestInfo<T>> duplicateIconRequests =
-                            duplicateIconRequestsMap.get(cn);
-
-                    if (cn != null) {
-                        CacheEntry entry = cacheLocked(
-                                cn,
-                                /* user = */ sectionKey.first,
-                                () -> duplicateIconRequests.get(0).launcherActivityInfo,
-                                mLauncherActivityInfoCachingLogic,
-                                c,
-                                /* usePackageIcon= */ false,
-                                /* useLowResIcons = */ sectionKey.second);
-
-                        for (IconRequestInfo<T> iconRequest : duplicateIconRequests) {
-                            applyCacheEntry(entry, iconRequest.itemInfo);
-                        }
-                    }
-                }
-            } catch (SQLiteException e) {
-                Log.d(TAG, "Error reading icon cache", e);
-            } finally {
-                Trace.endSection();
-            }
+            loadIconSubsection(sectionKey, filteredList, duplicateIconRequestsMap);
+            Trace.endSection();
         });
         Trace.endSection();
     }
 
+    private <T extends ItemInfoWithIcon> void loadIconSubsection(
+            Pair<UserHandle, Boolean> sectionKey,
+            List<IconRequestInfo<T>> filteredList,
+            Map<ComponentName, List<IconRequestInfo<T>>> duplicateIconRequestsMap) {
+        Trace.beginSection("loadIconSubsectionWithDatabase");
+        try (Cursor c = createBulkQueryCursor(
+                filteredList,
+                /* user = */ sectionKey.first,
+                /* useLowResIcons = */ sectionKey.second)) {
+            // Database title and icon loading
+            int componentNameColumnIndex = c.getColumnIndexOrThrow(IconDB.COLUMN_COMPONENT);
+            while (c.moveToNext()) {
+                ComponentName cn = ComponentName.unflattenFromString(
+                        c.getString(componentNameColumnIndex));
+                List<IconRequestInfo<T>> duplicateIconRequests =
+                        duplicateIconRequestsMap.get(cn);
+
+                if (cn != null) {
+                    CacheEntry entry = cacheLocked(
+                            cn,
+                            /* user = */ sectionKey.first,
+                            () -> duplicateIconRequests.get(0).launcherActivityInfo,
+                            mLauncherActivityInfoCachingLogic,
+                            c,
+                            /* usePackageIcon= */ false,
+                            /* useLowResIcons = */ sectionKey.second);
+
+                    for (IconRequestInfo<T> iconRequest : duplicateIconRequests) {
+                        applyCacheEntry(entry, iconRequest.itemInfo);
+                    }
+                }
+            }
+        } catch (SQLiteException e) {
+            Log.d(TAG, "Error reading icon cache", e);
+        } finally {
+            Trace.endSection();
+        }
+
+        Trace.beginSection("loadIconSubsectionWithFallback");
+        // Fallback title and icon loading
+        for (ComponentName cn : duplicateIconRequestsMap.keySet()) {
+            IconRequestInfo<T> iconRequestInfo = duplicateIconRequestsMap.get(cn).get(0);
+            ItemInfoWithIcon itemInfo = iconRequestInfo.itemInfo;
+            BitmapInfo icon = itemInfo.bitmap;
+            boolean loadFallbackTitle = TextUtils.isEmpty(itemInfo.title);
+            boolean loadFallbackIcon = icon == null
+                    || isDefaultIcon(icon, itemInfo.user)
+                    || icon == BitmapInfo.LOW_RES_INFO;
+
+            if (loadFallbackTitle || loadFallbackIcon) {
+                Log.i(TAG,
+                        "Database bulk icon loading failed, using fallback bulk icon loading "
+                                + "for: " + cn);
+                CacheEntry entry = new CacheEntry();
+                LauncherActivityInfo lai = iconRequestInfo.launcherActivityInfo;
+
+                // Fill fields that are not updated below so they are not subsequently
+                // deleted.
+                entry.title = itemInfo.title;
+                if (icon != null) {
+                    entry.bitmap = icon;
+                }
+                entry.contentDescription = itemInfo.contentDescription;
+
+                if (loadFallbackIcon) {
+                    loadFallbackIcon(
+                            lai,
+                            entry,
+                            mLauncherActivityInfoCachingLogic,
+                            /* usePackageIcon= */ false,
+                            /* usePackageTitle= */ loadFallbackTitle,
+                            cn,
+                            sectionKey.first);
+                }
+                if (loadFallbackTitle && TextUtils.isEmpty(entry.title)) {
+                    loadFallbackTitle(
+                            lai,
+                            entry,
+                            mLauncherActivityInfoCachingLogic,
+                            sectionKey.first);
+                }
+
+                for (IconRequestInfo<T> iconRequest : duplicateIconRequestsMap.get(cn)) {
+                    applyCacheEntry(entry, iconRequest.itemInfo);
+                }
+            }
+        }
+        Trace.endSection();
+    }
 
     /**
      * Fill in {@param infoInOut} with the corresponding icon and label.
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 4f40567..2bf3b14 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -75,13 +75,10 @@
             "home-to-overview-swipe-height";
     public static final String REQUEST_BACKGROUND_TO_OVERVIEW_SWIPE_HEIGHT =
             "background-to-overview-swipe-height";
-    public static final String REQUEST_ALL_APPS_TO_OVERVIEW_SWIPE_HEIGHT =
-            "all-apps-to-overview-swipe-height";
     public static final String REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT =
             "home-to-all-apps-swipe-height";
     public static final String REQUEST_ICON_HEIGHT =
             "icon-height";
-    public static final String REQUEST_HOTSEAT_TOP = "hotseat-top";
     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";
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index 106bb92..0f69530 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -247,11 +247,13 @@
      * @param originalView The View that the FloatingIconView will replace.
      * @param info ItemInfo of the originalView
      * @param pos The position of the view.
+     * @param btvIcon The drawable of the BubbleTextView. May be null if original view is not a BTV
+     * @param outIconLoadResult We store the icon results into this object.
      */
     @WorkerThread
     @SuppressWarnings("WrongThread")
     private static void getIconResult(Launcher l, View originalView, ItemInfo info, RectF pos,
-            Drawable btvIcon, IconLoadResult iconLoadResult) {
+            @Nullable Drawable btvIcon, IconLoadResult outIconLoadResult) {
         Drawable drawable;
         boolean supportsAdaptiveIcons = !info.isDisabled(); // Use original icon for disabled icons.
 
@@ -271,7 +273,9 @@
             int width = (int) pos.width();
             int height = (int) pos.height();
             if (supportsAdaptiveIcons) {
-                drawable = getFullDrawable(l, info, width, height, sTmpObjArray);
+                boolean shouldThemeIcon = btvIcon instanceof FastBitmapDrawable
+                        && ((FastBitmapDrawable) btvIcon).isThemed();
+                drawable = getFullDrawable(l, info, width, height, shouldThemeIcon, sTmpObjArray);
                 if (drawable instanceof AdaptiveIconDrawable) {
                     badge = getBadge(l, info, sTmpObjArray[0]);
                 } else {
@@ -284,24 +288,25 @@
                     // Similar to DragView, we simply use the BubbleTextView icon here.
                     drawable = btvIcon;
                 } else {
-                    drawable = getFullDrawable(l, info, width, height, sTmpObjArray);
+                    drawable = getFullDrawable(l, info, width, height, true /* shouldThemeIcon */,
+                            sTmpObjArray);
                 }
             }
         }
 
         drawable = drawable == null ? null : drawable.getConstantState().newDrawable();
         int iconOffset = getOffsetForIconBounds(l, drawable, pos);
-        synchronized (iconLoadResult) {
-            iconLoadResult.btvDrawable = btvIcon == null || drawable == btvIcon
+        synchronized (outIconLoadResult) {
+            outIconLoadResult.btvDrawable = btvIcon == null || drawable == btvIcon
                     ? null : btvIcon.getConstantState().newDrawable();
-            iconLoadResult.drawable = drawable;
-            iconLoadResult.badge = badge;
-            iconLoadResult.iconOffset = iconOffset;
-            if (iconLoadResult.onIconLoaded != null) {
-                l.getMainExecutor().execute(iconLoadResult.onIconLoaded);
-                iconLoadResult.onIconLoaded = null;
+            outIconLoadResult.drawable = drawable;
+            outIconLoadResult.badge = badge;
+            outIconLoadResult.iconOffset = iconOffset;
+            if (outIconLoadResult.onIconLoaded != null) {
+                l.getMainExecutor().execute(outIconLoadResult.onIconLoaded);
+                outIconLoadResult.onIconLoaded = null;
             }
-            iconLoadResult.isIconLoaded = true;
+            outIconLoadResult.isIconLoaded = true;
         }
     }
 
@@ -528,8 +533,7 @@
             btvIcon = null;
         }
 
-        IconLoadResult result = new IconLoadResult(info,
-                btvIcon == null ? false : btvIcon.isThemed());
+        IconLoadResult result = new IconLoadResult(info, btvIcon != null && btvIcon.isThemed());
         result.btvDrawable = btvIcon;
 
         final long fetchIconId = sFetchIconId++;
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 5afbee2..79a4673 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -509,7 +509,7 @@
                 "Launcher still active", launcher -> launcher == null, DEFAULT_UI_TIMEOUT);
     }
 
-    protected boolean isInBackground(Launcher launcher) {
+    protected boolean isInLaunchedApp(Launcher launcher) {
         return launcher == null || !launcher.hasBeenResumed();
     }
 
@@ -549,7 +549,7 @@
                             ordinal == TestProtocol.NORMAL_STATE_ORDINAL);
                     break;
                 }
-                case ALL_APPS: {
+                case HOME_ALL_APPS: {
                     assertTrue(
                             "Launcher is not resumed in state: " + expectedContainerType,
                             isResumed);
@@ -564,7 +564,7 @@
                             ordinal == TestProtocol.OVERVIEW_STATE_ORDINAL);
                     break;
                 }
-                case BACKGROUND: {
+                case LAUNCHED_APP: {
                     assertTrue("Launcher is resumed in state: " + expectedContainerType,
                             !isResumed);
                     assertTrue(TestProtocol.stateOrdinalToString(ordinal),
@@ -577,10 +577,10 @@
             }
         } else {
             assertTrue(
-                    "Container type is not BACKGROUND or FALLBACK_OVERVIEW: "
+                    "Container type is not LAUNCHED_APP or FALLBACK_OVERVIEW: "
                             + expectedContainerType,
-                    expectedContainerType == ContainerType.BACKGROUND ||
-                            expectedContainerType == ContainerType.FALLBACK_OVERVIEW);
+                    expectedContainerType == ContainerType.LAUNCHED_APP
+                            || expectedContainerType == ContainerType.FALLBACK_OVERVIEW);
         }
     }
 
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 2035da1..da8bf6e 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -38,6 +38,10 @@
 import com.android.launcher3.tapl.AppIconMenuItem;
 import com.android.launcher3.tapl.Folder;
 import com.android.launcher3.tapl.FolderIcon;
+import com.android.launcher3.tapl.HomeAllApps;
+import com.android.launcher3.tapl.HomeAppIcon;
+import com.android.launcher3.tapl.HomeAppIconMenu;
+import com.android.launcher3.tapl.HomeAppIconMenuItem;
 import com.android.launcher3.tapl.Widgets;
 import com.android.launcher3.tapl.Workspace;
 import com.android.launcher3.util.TestUtil;
@@ -215,7 +219,7 @@
         assertTrue("Launcher internal state is not Home", isInState(() -> LauncherState.NORMAL));
 
         // Test starting a workspace app.
-        final AppIcon app = workspace.getWorkspaceAppIcon("Chrome");
+        final HomeAppIcon app = workspace.getWorkspaceAppIcon("Chrome");
         assertNotNull("No Chrome app in workspace", app);
     }
 
@@ -228,7 +232,7 @@
                     "Launcher activity is the top activity; expecting another activity to be the "
                             + "top "
                             + "one",
-                    test.isInBackground(launcher)));
+                    test.isInLaunchedApp(launcher)));
         } finally {
             allApps.unfreeze();
         }
@@ -237,7 +241,7 @@
     @Test
     @PortraitLandscape
     public void testAppIconLaunchFromAllAppsFromHome() throws Exception {
-        final AllApps allApps = mLauncher.getWorkspace().switchToAllApps();
+        final HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
         assertTrue("Launcher internal state is not All Apps",
                 isInState(() -> LauncherState.ALL_APPS));
 
@@ -286,9 +290,7 @@
     @Test
     @PortraitLandscape
     public void testLaunchMenuItem() throws Exception {
-        final AllApps allApps = mLauncher.
-                getWorkspace().
-                switchToAllApps();
+        final AllApps allApps = mLauncher.getWorkspace().switchToAllApps();
         allApps.freeze();
         try {
             final AppIconMenu menu = allApps.
@@ -313,8 +315,7 @@
         // 1. Open all apps and wait for load complete.
         // 2. Drag icon to homescreen.
         // 3. Verify that the icon works on homescreen.
-        final AllApps allApps = mLauncher.getWorkspace().
-                switchToAllApps();
+        final HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
         allApps.freeze();
         try {
             allApps.getAppIcon(APP_NAME).dragToWorkspace(false, false);
@@ -325,7 +326,7 @@
         executeOnLauncher(launcher -> assertTrue(
                 "Launcher activity is the top activity; expecting another activity to be the top "
                         + "one",
-                isInBackground(launcher)));
+                isInLaunchedApp(launcher)));
     }
 
     @Test
@@ -334,18 +335,18 @@
         // 1. Open all apps and wait for load complete.
         // 2. Find the app and long press it to show shortcuts.
         // 3. Press icon center until shortcuts appear
-        final AllApps allApps = mLauncher
+        final HomeAllApps allApps = mLauncher
                 .getWorkspace()
                 .switchToAllApps();
         allApps.freeze();
         try {
-            final AppIconMenu menu = allApps
+            final HomeAppIconMenu menu = allApps
                     .getAppIcon(APP_NAME)
                     .openDeepShortcutMenu();
-            final AppIconMenuItem menuItem0 = menu.getMenuItem(0);
-            final AppIconMenuItem menuItem2 = menu.getMenuItem(2);
+            final HomeAppIconMenuItem menuItem0 = menu.getMenuItem(0);
+            final HomeAppIconMenuItem menuItem2 = menu.getMenuItem(2);
 
-            final AppIconMenuItem menuItem;
+            final HomeAppIconMenuItem menuItem;
 
             final String expectedShortcutName = "Shortcut 3";
             if (menuItem0.getText().equals(expectedShortcutName)) {
@@ -364,27 +365,27 @@
         }
     }
 
-    private AppIcon createShortcutIfNotExist(String name) {
-        AppIcon appIcon = mLauncher.getWorkspace().tryGetWorkspaceAppIcon(name);
-        if (appIcon == null) {
-            AllApps allApps = mLauncher.getWorkspace().switchToAllApps();
+    private HomeAppIcon createShortcutIfNotExist(String name) {
+        HomeAppIcon homeAppIcon = mLauncher.getWorkspace().tryGetWorkspaceAppIcon(name);
+        if (homeAppIcon == null) {
+            HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
             allApps.freeze();
             try {
                 allApps.getAppIcon(name).dragToWorkspace(false, false);
             } finally {
                 allApps.unfreeze();
             }
-            appIcon = mLauncher.getWorkspace().getWorkspaceAppIcon(name);
+            homeAppIcon = mLauncher.getWorkspace().getWorkspaceAppIcon(name);
         }
-        return appIcon;
+        return homeAppIcon;
     }
 
     @Ignore("b/205014516")
     @Test
     @PortraitLandscape
     public void testDragToFolder() throws Exception {
-        final AppIcon playStoreIcon = createShortcutIfNotExist("Play Store");
-        final AppIcon gmailIcon = createShortcutIfNotExist("Gmail");
+        final HomeAppIcon playStoreIcon = createShortcutIfNotExist("Play Store");
+        final HomeAppIcon gmailIcon = createShortcutIfNotExist("Gmail");
 
         FolderIcon folderIcon = gmailIcon.dragToIcon(playStoreIcon);
 
@@ -398,7 +399,7 @@
         assertNull("Play Store should be moved to a folder.",
                 workspace.tryGetWorkspaceAppIcon("Play Store"));
 
-        final AppIcon youTubeIcon = createShortcutIfNotExist("YouTube");
+        final HomeAppIcon youTubeIcon = createShortcutIfNotExist("YouTube");
 
         folderIcon = youTubeIcon.dragToIcon(folderIcon);
         folder = folderIcon.open();
@@ -415,7 +416,7 @@
         mLauncher.getWorkspace();
         waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
 
-        AllApps allApps = mLauncher.getWorkspace().switchToAllApps();
+        HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
         allApps.freeze();
         try {
             allApps.getAppIcon(APP_NAME).dragToWorkspace(false, false);
@@ -432,16 +433,16 @@
     @PortraitLandscape
     public void testDeleteFromWorkspace() throws Exception {
         // test delete both built-in apps and user-installed app from workspace
-        for (String appName : new String[]{"Gmail", "Play Store", APP_NAME}) {
-            final AppIcon appIcon = createShortcutIfNotExist(appName);
-            Workspace workspace = mLauncher.getWorkspace().deleteAppIcon(appIcon);
+        for (String appName : new String[] {"Gmail", "Play Store", APP_NAME}) {
+            final HomeAppIcon homeAppIcon = createShortcutIfNotExist(appName);
+            Workspace workspace = mLauncher.getWorkspace().deleteAppIcon(homeAppIcon);
             assertNull(appName + " app was found after being deleted from workspace",
                     workspace.tryGetWorkspaceAppIcon(appName));
         }
     }
 
     private void verifyAppUninstalledFromAllApps(Workspace workspace, String appName) {
-        final AllApps allApps = workspace.switchToAllApps();
+        final HomeAllApps allApps = workspace.switchToAllApps();
         allApps.freeze();
         try {
             assertNull(appName + " app was found on all apps after being uninstalled",
@@ -469,7 +470,7 @@
         TestUtil.installDummyApp();
         try {
             Workspace workspace = mLauncher.getWorkspace();
-            final AllApps allApps = workspace.switchToAllApps();
+            final HomeAllApps allApps = workspace.switchToAllApps();
             allApps.freeze();
             try {
                 workspace = allApps.getAppIcon(DUMMY_APP_NAME).uninstall();
@@ -496,7 +497,7 @@
         };
 
         for (Point target : targets) {
-            final AllApps allApps = mLauncher.getWorkspace().switchToAllApps();
+            final HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
             allApps.freeze();
             try {
                 allApps.getAppIcon(APP_NAME).dragToWorkspace(target.x, target.y);
@@ -508,7 +509,7 @@
         }
 
         // test to move a shortcut to other cell.
-        final AppIcon launcherTestAppIcon = createShortcutIfNotExist(APP_NAME);
+        final HomeAppIcon launcherTestAppIcon = createShortcutIfNotExist(APP_NAME);
         for (Point target : targets) {
             launcherTestAppIcon.dragToWorkspace(target.x, target.y);
         }
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index 800322b..3658f41 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -36,7 +36,7 @@
 /**
  * Operations on AllApps opened from Home. Also a parent for All Apps opened from Overview.
  */
-public class AllApps extends LauncherInstrumentation.VisibleContainer {
+public abstract class AllApps extends LauncherInstrumentation.VisibleContainer {
     private static final int MAX_SCROLL_ATTEMPTS = 40;
 
     private final int mHeight;
@@ -55,11 +55,6 @@
                 .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
     }
 
-    @Override
-    protected LauncherInstrumentation.ContainerType getContainerType() {
-        return LauncherInstrumentation.ContainerType.ALL_APPS;
-    }
-
     private boolean hasClickableIcon(UiObject2 allAppsContainer, UiObject2 appListRecycler,
             BySelector appIconSelector, int displayBottom) {
         final UiObject2 icon;
@@ -157,7 +152,7 @@
 
                 final UiObject2 appIcon = mLauncher.waitForObjectInContainer(appListRecycler,
                         appIconSelector);
-                return new AllAppsAppIcon(mLauncher, appIcon);
+                return createAppIcon(appIcon);
             } else {
                 return null;
             }
@@ -178,6 +173,8 @@
         return appIcon;
     }
 
+    protected abstract AppIcon createAppIcon(UiObject2 icon);
+
     private void scrollBackToBeginning() {
         try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                 "want to scroll back in all apps")) {
diff --git a/tests/tapl/com/android/launcher3/tapl/AllAppsAppIcon.java b/tests/tapl/com/android/launcher3/tapl/AllAppsAppIcon.java
index 03d387e..8adce29 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllAppsAppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllAppsAppIcon.java
@@ -23,7 +23,7 @@
 /**
  * App icon in all apps.
  */
-final class AllAppsAppIcon extends AppIcon {
+final class AllAppsAppIcon extends HomeAppIcon {
 
     private static final Pattern LONG_CLICK_EVENT = Pattern.compile("onAllAppsItemLongClick");
 
diff --git a/tests/tapl/com/android/launcher3/tapl/AllAppsFromOverview.java b/tests/tapl/com/android/launcher3/tapl/AllAppsFromOverview.java
deleted file mode 100644
index 835790d..0000000
--- a/tests/tapl/com/android/launcher3/tapl/AllAppsFromOverview.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2018 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.OVERVIEW_STATE_ORDINAL;
-
-import android.graphics.Point;
-
-import androidx.annotation.NonNull;
-import androidx.test.uiautomator.UiObject2;
-
-import com.android.launcher3.testing.TestProtocol;
-
-/**
- * Operations on AllApps opened from Overview.
- */
-public final class AllAppsFromOverview extends AllApps {
-
-    AllAppsFromOverview(LauncherInstrumentation launcher) {
-        super(launcher);
-        verifyActiveContainer();
-    }
-
-    /**
-     * Swipes down to switch back to Overview whence we came from.
-     *
-     * @return the overview panel.
-     */
-    @NonNull
-    public Overview switchBackToOverview() {
-        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
-             LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
-                     "want to switch back from all apps to overview")) {
-            final UiObject2 allAppsContainer = verifyActiveContainer();
-            // Swipe from the search box to the bottom.
-            final UiObject2 qsb = mLauncher.waitForObjectInContainer(
-                    allAppsContainer, "search_container_all_apps");
-            final Point start = qsb.getVisibleCenter();
-            final int swipeHeight = mLauncher.getTestInfo(
-                    TestProtocol.REQUEST_ALL_APPS_TO_OVERVIEW_SWIPE_HEIGHT).
-                    getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
-
-            final int endY = start.y + swipeHeight;
-            LauncherInstrumentation.log("AllAppsFromOverview.switchBackToOverview before swipe");
-            mLauncher.swipeToState(start.x, start.y, start.x, endY, 60, OVERVIEW_STATE_ORDINAL,
-                    LauncherInstrumentation.GestureScope.INSIDE);
-
-            try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("swiped down")) {
-                return new Overview(mLauncher);
-            }
-        }
-    }
-}
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIcon.java b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
index 50611d7..bef242c 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
@@ -16,36 +16,33 @@
 
 package com.android.launcher3.tapl;
 
-import android.graphics.Point;
-import android.graphics.Rect;
 import android.widget.TextView;
 
-import androidx.annotation.NonNull;
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.BySelector;
 import androidx.test.uiautomator.UiObject2;
 
 import com.android.launcher3.testing.TestProtocol;
 
-import java.util.function.Supplier;
 import java.util.regex.Pattern;
 
 /**
- * App icon, whether in all apps or in workspace/
+ * App icon, whether in all apps, workspace or the taskbar.
  */
-public abstract class AppIcon extends Launchable implements FolderDragTarget {
-
-    private final String mAppName;
+public abstract class AppIcon extends Launchable {
 
     AppIcon(LauncherInstrumentation launcher, UiObject2 icon) {
         super(launcher, icon);
-        mAppName = icon.getText();
     }
 
     static BySelector getAppIconSelector(String appName, LauncherInstrumentation launcher) {
         return By.clazz(TextView.class).text(appName).pkg(launcher.getLauncherPackageName());
     }
 
+    static BySelector getAnyAppIconSelector() {
+        return By.clazz(TextView.class);
+    }
+
     protected abstract Pattern getLongClickEvent();
 
     /**
@@ -53,7 +50,7 @@
      */
     public AppIconMenu openMenu() {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
-            return new AppIconMenu(mLauncher, mLauncher.clickAndGet(
+            return createMenu(mLauncher.clickAndGet(
                     mObject, "popup_container", getLongClickEvent()));
         }
     }
@@ -63,33 +60,12 @@
      */
     public AppIconMenu openDeepShortcutMenu() {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
-            return new AppIconMenu(mLauncher, mLauncher.clickAndGet(
+            return createMenu(mLauncher.clickAndGet(
                     mObject, "deep_shortcuts_container", getLongClickEvent()));
         }
     }
 
-    /**
-     * Drag the AppIcon to the given position of other icon. The drag must result in a folder.
-     *
-     * @param target the destination icon.
-     */
-    @NonNull
-    public FolderIcon dragToIcon(FolderDragTarget target) {
-        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
-             LauncherInstrumentation.Closable c = mLauncher.addContextLayer("want to drag icon")) {
-            final Rect dropBounds = target.getDropLocationBounds();
-            Workspace.dragIconToWorkspace(
-                    mLauncher, this,
-                    () -> {
-                        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;
-        }
-    }
+    protected abstract AppIconMenu createMenu(UiObject2 menu);
 
     @Override
     protected void addExpectedEventsForLongClick() {
@@ -110,63 +86,4 @@
     protected String launchableType() {
         return "app icon";
     }
-
-    @Override
-    public Rect getDropLocationBounds() {
-        return mLauncher.getVisibleBounds(mObject);
-    }
-
-    @Override
-    public FolderIcon getTargetFolder(Rect bounds) {
-        for (FolderIcon folderIcon : mLauncher.getWorkspace().getFolderIcons()) {
-            final Rect folderIconBounds = folderIcon.getDropLocationBounds();
-            if (bounds.contains(folderIconBounds.centerX(), folderIconBounds.centerY())) {
-                return folderIcon;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Uninstall the appIcon by dragging it to the 'uninstall' drop point of the drop_target_bar.
-     *
-     * @return validated workspace after the existing appIcon being uninstalled.
-     */
-    public Workspace uninstall() {
-        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
-             LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
-                     "uninstalling app icon")) {
-            return Workspace.uninstallAppIcon(
-                    mLauncher, this,
-                    this::addExpectedEventsForLongClick
-            );
-        }
-    }
-
-    /**
-     * Drag an object to the given cell in workspace. The target cell must be empty.
-     *
-     * @param cellX zero based column number, starting from the left of the screen.
-     * @param cellY zero based row number, starting from the top of the screen.
-     */
-    public AppIcon dragToWorkspace(int cellX, int cellY) {
-        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
-             LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
-                     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, true, getLongPressIndicator(),
-                    () -> addExpectedEventsForLongClick(), null);
-            try (LauncherInstrumentation.Closable ignore = mLauncher.addContextLayer("dragged")) {
-                WorkspaceAppIcon appIcon =
-                        (WorkspaceAppIcon) mLauncher.getWorkspace().getWorkspaceAppIcon(mAppName);
-                mLauncher.assertTrue(
-                        String.format(
-                                "The %s icon should be in the cell (%d, %d).", mAppName, cellX,
-                                cellY),
-                        appIcon.isInCell(cellX, cellY));
-                return appIcon;
-            }
-        }
-    }
-}
\ No newline at end of file
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java b/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java
index 7f28151..82d9630 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java
@@ -25,9 +25,9 @@
 /**
  * Context menu of an app icon.
  */
-public class AppIconMenu {
-    private final LauncherInstrumentation mLauncher;
-    private final UiObject2 mDeepShortcutsContainer;
+public abstract class AppIconMenu {
+    protected final LauncherInstrumentation mLauncher;
+    protected final UiObject2 mDeepShortcutsContainer;
 
     AppIconMenu(LauncherInstrumentation launcher,
             UiObject2 deepShortcutsContainer) {
@@ -42,6 +42,17 @@
         final List<UiObject2> menuItems = mLauncher.getObjectsInContainer(mDeepShortcutsContainer,
                 "bubble_text");
         assertTrue(menuItems.size() > itemNumber);
-        return new AppIconMenuItem(mLauncher, menuItems.get(itemNumber));
+        return createMenuItem(menuItems.get(itemNumber));
     }
+
+    /**
+     * Returns a menu item with the given text. Fails if it doesn't exist.
+     */
+    public AppIconMenuItem getMenuItem(String shortcutText) {
+        final UiObject2 menuItem = mLauncher.waitForObjectInContainer(mDeepShortcutsContainer,
+                AppIcon.getAppIconSelector(shortcutText, mLauncher));
+        return createMenuItem(menuItem);
+    }
+
+    protected abstract AppIconMenuItem createMenuItem(UiObject2 menuItem);
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java b/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java
index ac0db08..a6a1531 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java
@@ -23,7 +23,7 @@
 /**
  * Menu item in an app icon menu.
  */
-public class AppIconMenuItem extends Launchable {
+public abstract class AppIconMenuItem extends Launchable {
 
     AppIconMenuItem(LauncherInstrumentation launcher, UiObject2 shortcut) {
         super(launcher, shortcut);
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index 4333b27..589e13c 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -37,7 +37,7 @@
  * Indicates the base state with a UI other than Overview running as foreground. It can also
  * indicate Launcher as long as Launcher is not in Overview state.
  */
-public class Background extends LauncherInstrumentation.VisibleContainer {
+public abstract class Background extends LauncherInstrumentation.VisibleContainer {
     private static final int ZERO_BUTTON_SWIPE_UP_GESTURE_DURATION = 500;
     private static final Pattern SQUARE_BUTTON_EVENT = Pattern.compile("onOverviewToggle");
 
@@ -45,11 +45,6 @@
         super(launcher);
     }
 
-    @Override
-    protected LauncherInstrumentation.ContainerType getContainerType() {
-        return LauncherInstrumentation.ContainerType.BACKGROUND;
-    }
-
     /**
      * Swipes up or presses the square button to switch to Overview.
      * Returns the base overview, which can be either in Launcher or the fallback recents.
@@ -212,17 +207,17 @@
     }
 
     @NonNull
-    public Background quickSwitchToPreviousApp() {
+    public LaunchedAppState quickSwitchToPreviousApp() {
         boolean toRight = true;
         quickSwitch(toRight);
-        return new Background(mLauncher);
+        return new LaunchedAppState(mLauncher);
     }
 
     @NonNull
-    public Background quickSwitchToPreviousAppSwipeLeft() {
+    public LaunchedAppState quickSwitchToPreviousAppSwipeLeft() {
         boolean toRight = false;
         quickSwitch(toRight);
-        return new Background(mLauncher);
+        return new LaunchedAppState(mLauncher);
     }
 
     @NonNull
diff --git a/tests/tapl/com/android/launcher3/tapl/Folder.java b/tests/tapl/com/android/launcher3/tapl/Folder.java
index f046b6e..26f0a8b 100644
--- a/tests/tapl/com/android/launcher3/tapl/Folder.java
+++ b/tests/tapl/com/android/launcher3/tapl/Folder.java
@@ -40,7 +40,7 @@
      * Find an app icon with given name or raise assertion error.
      */
     @NonNull
-    public AppIcon getAppIcon(String appName) {
+    public HomeAppIcon getAppIcon(String appName) {
         try (LauncherInstrumentation.Closable ignored = mLauncher.addContextLayer(
                 "Want to get app icon in folder")) {
             return new WorkspaceAppIcon(mLauncher,
diff --git a/tests/tapl/com/android/launcher3/tapl/FolderDragTarget.java b/tests/tapl/com/android/launcher3/tapl/FolderDragTarget.java
index d797418..2c60668 100644
--- a/tests/tapl/com/android/launcher3/tapl/FolderDragTarget.java
+++ b/tests/tapl/com/android/launcher3/tapl/FolderDragTarget.java
@@ -19,7 +19,10 @@
 import android.graphics.Rect;
 
 public interface FolderDragTarget {
+
+    /** This method requires public access, however should not be called in tests. */
     Rect getDropLocationBounds();
 
+    /** This method requires public access, however should not be called in tests. */
     FolderIcon getTargetFolder(Rect bounds);
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/FolderIcon.java b/tests/tapl/com/android/launcher3/tapl/FolderIcon.java
index 2e79d70..9b4717f 100644
--- a/tests/tapl/com/android/launcher3/tapl/FolderIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/FolderIcon.java
@@ -52,11 +52,13 @@
         return new Folder(mLauncher);
     }
 
+    /** This method requires public access, however should not be called in tests. */
     @Override
     public Rect getDropLocationBounds() {
         return mLauncher.getVisibleBounds(mObject.getParent());
     }
 
+    /** This method requires public access, however should not be called in tests. */
     @Override
     public FolderIcon getTargetFolder(Rect bounds) {
         return this;
diff --git a/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java b/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java
new file mode 100644
index 0000000..2e00d59
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java
@@ -0,0 +1,42 @@
+/*
+ * 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;
+
+public class HomeAllApps extends AllApps {
+
+    HomeAllApps(LauncherInstrumentation launcher) {
+        super(launcher);
+    }
+
+    @Override
+    protected LauncherInstrumentation.ContainerType getContainerType() {
+        return LauncherInstrumentation.ContainerType.HOME_ALL_APPS;
+    }
+
+    @NonNull
+    @Override
+    public HomeAppIcon getAppIcon(String appName) {
+        return (AllAppsAppIcon) super.getAppIcon(appName);
+    }
+
+    @Override
+    protected HomeAppIcon createAppIcon(UiObject2 icon) {
+        return new AllAppsAppIcon(mLauncher, icon);
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/HomeAppIcon.java b/tests/tapl/com/android/launcher3/tapl/HomeAppIcon.java
new file mode 100644
index 0000000..4b36dc0
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/HomeAppIcon.java
@@ -0,0 +1,139 @@
+/*
+ * 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 android.graphics.Point;
+import android.graphics.Rect;
+
+import androidx.annotation.NonNull;
+import androidx.test.uiautomator.UiObject2;
+
+import java.util.function.Supplier;
+
+/**
+ * App icon on the workspace or all apps.
+ */
+public abstract class HomeAppIcon extends AppIcon implements FolderDragTarget, WorkspaceDragSource {
+
+    private final String mAppName;
+
+    HomeAppIcon(LauncherInstrumentation launcher, UiObject2 icon) {
+        super(launcher, icon);
+        mAppName = icon.getText();
+    }
+
+    /**
+     * Drag the AppIcon to the given position of other icon. The drag must result in a folder.
+     *
+     * @param target the destination icon.
+     */
+    @NonNull
+    public FolderIcon dragToIcon(FolderDragTarget target) {
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+             LauncherInstrumentation.Closable c = mLauncher.addContextLayer("want to drag icon")) {
+            final Rect dropBounds = target.getDropLocationBounds();
+            Workspace.dragIconToWorkspace(
+                    mLauncher, this,
+                    () -> {
+                        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;
+        }
+    }
+
+    /** This method requires public access, however should not be called in tests. */
+    @Override
+    public Rect getDropLocationBounds() {
+        return mLauncher.getVisibleBounds(mObject);
+    }
+
+    /** This method requires public access, however should not be called in tests. */
+    @Override
+    public FolderIcon getTargetFolder(Rect bounds) {
+        for (FolderIcon folderIcon : mLauncher.getWorkspace().getFolderIcons()) {
+            final Rect folderIconBounds = folderIcon.getDropLocationBounds();
+            if (bounds.contains(folderIconBounds.centerX(), folderIconBounds.centerY())) {
+                return folderIcon;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public HomeAppIconMenu openDeepShortcutMenu() {
+        return (HomeAppIconMenu) super.openDeepShortcutMenu();
+    }
+
+    @Override
+    protected HomeAppIconMenu createMenu(UiObject2 menu) {
+        return new HomeAppIconMenu(mLauncher, menu);
+    }
+
+    /**
+     * Uninstall the appIcon by dragging it to the 'uninstall' drop point of the drop_target_bar.
+     *
+     * @return validated workspace after the existing appIcon being uninstalled.
+     */
+    public Workspace uninstall() {
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+             LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                     "uninstalling app icon")) {
+            return Workspace.uninstallAppIcon(
+                    mLauncher, this,
+                    this::addExpectedEventsForLongClick
+            );
+        }
+    }
+
+    /**
+     * Drag an object to the given cell in workspace. The target cell must be empty.
+     *
+     * @param cellX zero based column number, starting from the left of the screen.
+     * @param cellY zero based row number, starting from the top of the screen.
+     */
+    public HomeAppIcon dragToWorkspace(int cellX, int cellY) {
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+             LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                     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, true, getLongPressIndicator(),
+                    () -> addExpectedEventsForLongClick(), null);
+            try (LauncherInstrumentation.Closable ignore = mLauncher.addContextLayer("dragged")) {
+                WorkspaceAppIcon appIcon =
+                        (WorkspaceAppIcon) mLauncher.getWorkspace().getWorkspaceAppIcon(mAppName);
+                mLauncher.assertTrue(
+                        String.format(
+                                "The %s icon should be in the cell (%d, %d).", mAppName, cellX,
+                                cellY),
+                        appIcon.isInCell(cellX, cellY));
+                return appIcon;
+            }
+        }
+    }
+
+
+    /** This method requires public access, however should not be called in tests. */
+    @Override
+    public Launchable getLaunchable() {
+        return this;
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/HomeAppIconMenu.java b/tests/tapl/com/android/launcher3/tapl/HomeAppIconMenu.java
new file mode 100644
index 0000000..71fb6c0
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/HomeAppIconMenu.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;
+
+import androidx.test.uiautomator.UiObject2;
+
+/**
+ * Context menu of a home screen app icon.
+ */
+public final class HomeAppIconMenu extends AppIconMenu {
+
+    HomeAppIconMenu(LauncherInstrumentation launcher,
+            UiObject2 deepShortcutsContainer) {
+        super(launcher, deepShortcutsContainer);
+    }
+
+    @Override
+    public HomeAppIconMenuItem getMenuItem(int itemNumber) {
+        return (HomeAppIconMenuItem) super.getMenuItem(itemNumber);
+    }
+
+    @Override
+    protected HomeAppIconMenuItem createMenuItem(UiObject2 menuItem) {
+        return new HomeAppIconMenuItem(mLauncher, menuItem);
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/HomeAppIconMenuItem.java b/tests/tapl/com/android/launcher3/tapl/HomeAppIconMenuItem.java
new file mode 100644
index 0000000..1ff0c10
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/HomeAppIconMenuItem.java
@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+/**
+ * Menu item in a home screen app icon menu.
+ */
+public final class HomeAppIconMenuItem extends AppIconMenuItem implements WorkspaceDragSource {
+
+    HomeAppIconMenuItem(LauncherInstrumentation launcher,
+            UiObject2 shortcut) {
+        super(launcher, shortcut);
+    }
+
+    /** This method requires public access, however should not be called in tests. */
+    @Override
+    public Launchable getLaunchable() {
+        return this;
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index 7ec5208..02a6b91 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -18,17 +18,25 @@
 
 import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
 
+import static com.android.launcher3.testing.TestProtocol.SPRING_LOADED_STATE_ORDINAL;
+
 import android.graphics.Point;
+import android.view.MotionEvent;
 
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.BySelector;
 import androidx.test.uiautomator.UiObject2;
 import androidx.test.uiautomator.Until;
 
+import com.android.launcher3.testing.TestProtocol;
+
 /**
  * Ancestor for AppIcon and AppMenuItem.
  */
 abstract class Launchable {
+
+    protected static final int DEFAULT_DRAG_STEPS = 10;
+
     protected final LauncherInstrumentation mLauncher;
 
     protected final UiObject2 mObject;
@@ -45,7 +53,7 @@
     /**
      * Clicks the object to launch its app.
      */
-    public Background launch(String expectedPackageName) {
+    public LaunchedAppState launch(String expectedPackageName) {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
             return launch(By.pkg(expectedPackageName));
         }
@@ -55,56 +63,88 @@
 
     protected abstract String launchableType();
 
-    private Background launch(BySelector selector) {
+    private LaunchedAppState launch(BySelector selector) {
         try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                 "want to launch an app from " + launchableType())) {
             LauncherInstrumentation.log("Launchable.launch before click "
                     + mObject.getVisibleCenter() + " in " + mLauncher.getVisibleBounds(mObject));
             final String label = mObject.getText();
 
-            mLauncher.executeAndWaitForEvent(
-                    () -> {
-                        mLauncher.clickLauncherObject(mObject);
-                        expectActivityStartEvents();
-                    },
-                    event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED,
-                    () -> "Launching an app didn't open a new window: " + label,
-                    "clicking " + launchableType());
+            executeAndWaitForWindowChange(() -> {
+                mLauncher.clickLauncherObject(mObject);
+                expectActivityStartEvents();
+            }, label, "clicking " + launchableType());
 
             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("clicked")) {
-                mLauncher.assertTrue(
-                        "App didn't start: " + label + " (" + selector + ")",
-                        TestHelpers.wait(Until.hasObject(selector),
-                                LauncherInstrumentation.WAIT_TIME_MS));
-                return new Background(mLauncher);
+                return assertAppLaunched(label, selector);
             }
         }
     }
 
-    /**
-     * Drags an object to the center of homescreen.
-     *
-     * @param startsActivity   whether it's expected to start an activity.
-     * @param isWidgetShortcut whether we drag a widget shortcut
-     */
-    public void dragToWorkspace(boolean startsActivity, boolean isWidgetShortcut) {
-        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
-            final Point launchableCenter = getObject().getVisibleCenter();
-            final Point displaySize = mLauncher.getRealDisplaySize();
-            final int width = displaySize.x / 2;
-            Workspace.dragIconToWorkspace(
-                    mLauncher,
-                    this,
-                    new Point(
-                            launchableCenter.x >= width
-                                    ? launchableCenter.x - width / 2
-                                    : launchableCenter.x + width / 2,
-                            displaySize.y / 2),
-                    getLongPressIndicator(),
-                    startsActivity,
-                    isWidgetShortcut,
-                    () -> addExpectedEventsForLongClick());
+    protected void executeAndWaitForWindowChange(Runnable command, String label, String action) {
+        mLauncher.executeAndWaitForEvent(
+                command,
+                event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED,
+                () -> "Launching an app didn't open a new window: " + label,
+                action);
+    }
+
+    protected LaunchedAppState assertAppLaunched(String label, BySelector selector) {
+        mLauncher.assertTrue(
+                "App didn't start: " + label + " (" + selector + ")",
+                TestHelpers.wait(Until.hasObject(selector),
+                        LauncherInstrumentation.WAIT_TIME_MS));
+        return new LaunchedAppState(mLauncher);
+    }
+
+    Point startDrag(long downTime, String longPressIndicator,
+            Runnable expectLongClickEvents, boolean runToSpringLoadedState) {
+        final Point iconCenter = getObject().getVisibleCenter();
+        final Point dragStartCenter = new Point(iconCenter.x,
+                iconCenter.y - getStartDragThreshold());
+
+        if (runToSpringLoadedState) {
+            mLauncher.runToState(() -> movePointerForStartDrag(
+                    downTime,
+                    iconCenter,
+                    dragStartCenter,
+                    longPressIndicator,
+                    expectLongClickEvents),
+                    SPRING_LOADED_STATE_ORDINAL, "long-pressing and triggering drag start");
+        } else {
+            movePointerForStartDrag(
+                    downTime,
+                    iconCenter,
+                    dragStartCenter,
+                    longPressIndicator,
+                    expectLongClickEvents);
         }
+
+
+        return dragStartCenter;
+    }
+
+    /**
+     * 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);
+        LauncherInstrumentation.log("movePointerForStartDrag: sent down");
+        expectLongClickEvents.run();
+        mLauncher.waitForLauncherObject(longPressIndicator);
+        LauncherInstrumentation.log("movePointerForStartDrag: indicator");
+        mLauncher.movePointer(iconCenter, dragStartCenter, DEFAULT_DRAG_STEPS, false,
+                downTime, true, LauncherInstrumentation.GestureScope.INSIDE);
+    }
+
+    private int getStartDragThreshold() {
+        return mLauncher.getTestInfo(TestProtocol.REQUEST_START_DRAG_THRESHOLD).getInt(
+                TestProtocol.TEST_INFO_RESPONSE_FIELD);
     }
 
     protected abstract void addExpectedEventsForLongClick();
diff --git a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
new file mode 100644
index 0000000..3f1be80
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+/**
+ * Background state operations specific to when an app has been launched.
+ */
+public final class LaunchedAppState extends Background {
+
+    LaunchedAppState(LauncherInstrumentation launcher) {
+        super(launcher);
+    }
+
+    @Override
+    protected LauncherInstrumentation.ContainerType getContainerType() {
+        return LauncherInstrumentation.ContainerType.LAUNCHED_APP;
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 7c377d5..f063191 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -91,6 +91,7 @@
 import java.util.function.Supplier;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * The main tapl object. The only object that can be explicitly constructed by the using code. It
@@ -122,7 +123,7 @@
     // 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, ALL_APPS, OVERVIEW, WIDGETS, BACKGROUND, FALLBACK_OVERVIEW
+        WORKSPACE, HOME_ALL_APPS, OVERVIEW, WIDGETS, FALLBACK_OVERVIEW, LAUNCHED_APP
     }
 
     public enum NavigationModel {ZERO_BUTTON, THREE_BUTTON}
@@ -135,7 +136,7 @@
     }
 
     // Base class for launcher containers.
-    static abstract class VisibleContainer {
+    abstract static class VisibleContainer {
         protected final LauncherInstrumentation mLauncher;
 
         protected VisibleContainer(LauncherInstrumentation launcher) {
@@ -500,17 +501,20 @@
     }
 
     private String getVisiblePackages() {
-        final String apps = mDevice.findObjects(getAnyObjectSelector())
-                .stream()
-                .map(LauncherInstrumentation::getApplicationPackageSafe)
-                .distinct()
-                .filter(pkg -> pkg != null && !SYSTEMUI_PACKAGE.equals(pkg))
-                .collect(Collectors.joining(", "));
+        final String apps = getVisiblePackagesStream().collect(Collectors.joining(", "));
         return !apps.isEmpty()
                 ? "active app: " + apps
                 : "the test doesn't see views from any app, including Launcher";
     }
 
+    private Stream<String> getVisiblePackagesStream() {
+        return mDevice.findObjects(getAnyObjectSelector())
+                .stream()
+                .map(LauncherInstrumentation::getApplicationPackageSafe)
+                .distinct()
+                .filter(pkg -> pkg != null && !SYSTEMUI_PACKAGE.equals(pkg));
+    }
+
     private static String getApplicationPackageSafe(UiObject2 object) {
         try {
             return object.getApplicationPackage();
@@ -526,7 +530,7 @@
         if (hasLauncherObject(OVERVIEW_RES_ID)) return "Overview";
         if (hasLauncherObject(WORKSPACE_RES_ID)) return "Workspace";
         if (hasLauncherObject(APPS_RES_ID)) return "AllApps";
-        return "Background (" + getVisiblePackages() + ")";
+        return "LaunchedApp (" + getVisiblePackages() + ")";
     }
 
     public void setSystemHealthSupplier(Function<Long, String> supplier) {
@@ -726,7 +730,7 @@
                     waitUntilLauncherObjectGone(OVERVIEW_RES_ID);
                     return waitForLauncherObject(WIDGETS_RES_ID);
                 }
-                case ALL_APPS: {
+                case HOME_ALL_APPS: {
                     waitUntilLauncherObjectGone(WORKSPACE_RES_ID);
                     waitUntilLauncherObjectGone(OVERVIEW_RES_ID);
                     waitUntilLauncherObjectGone(WIDGETS_RES_ID);
@@ -736,13 +740,12 @@
                     waitUntilLauncherObjectGone(APPS_RES_ID);
                     waitUntilLauncherObjectGone(WORKSPACE_RES_ID);
                     waitUntilLauncherObjectGone(WIDGETS_RES_ID);
-
                     return waitForLauncherObject(OVERVIEW_RES_ID);
                 }
                 case FALLBACK_OVERVIEW: {
                     return waitForFallbackLauncherObject(OVERVIEW_RES_ID);
                 }
-                case BACKGROUND: {
+                case LAUNCHED_APP: {
                     waitUntilLauncherObjectGone(WORKSPACE_RES_ID);
                     waitUntilLauncherObjectGone(APPS_RES_ID);
                     waitUntilLauncherObjectGone(OVERVIEW_RES_ID);
@@ -823,6 +826,8 @@
         }
 
         GestureScope gestureScope = gestureStartFromLauncher
+                // Without the navigation bar layer, the gesture scope on tablets remains inside the
+                // launcher process.
                 ? (isTablet() ? GestureScope.INSIDE : GestureScope.INSIDE_TO_OUTSIDE)
                 : GestureScope.OUTSIDE_WITH_PILFER;
         linearGesture(
@@ -960,14 +965,14 @@
     }
 
     /**
-     * Gets the Workspace object if the current state is "background home", i.e. some other app is
-     * active. Fails if the launcher is not in that state.
+     * Gets the LaunchedApp object if another app is active. Fails if the launcher is not in that
+     * state.
      *
-     * @return Background object.
+     * @return LaunchedApp object.
      */
     @NonNull
-    public Background getBackground() {
-        return new Background(this);
+    public LaunchedAppState getLaunchedAppState() {
+        return new LaunchedAppState(this);
     }
 
     /**
@@ -1004,32 +1009,17 @@
     }
 
     /**
-     * Gets the All Apps object if the current state is showing the all apps panel opened by swiping
-     * from workspace. Fails if the launcher is not in that state. Please don't call this method if
-     * App Apps was opened by swiping up from Overview, as it won't fail and will return an
-     * incorrect object.
+     * Gets the homescreen All Apps object if the current state is showing the all apps panel opened
+     * by swiping from workspace. Fails if the launcher is not in that state. Please don't call this
+     * method if App Apps was opened by swiping up from Overview, as it won't fail and will return
+     * an incorrect object.
      *
-     * @return All Aps object.
+     * @return Home All Apps object.
      */
     @NonNull
-    public AllApps getAllApps() {
+    public HomeAllApps getAllApps() {
         try (LauncherInstrumentation.Closable c = addContextLayer("want to get all apps object")) {
-            return new AllApps(this);
-        }
-    }
-
-    /**
-     * Gets the All Apps object if the current state is showing the all apps panel opened by swiping
-     * from overview. Fails if the launcher is not in that state. Please don't call this method if
-     * App Apps was opened by swiping up from home, as it won't fail and will return an
-     * incorrect object.
-     *
-     * @return All Aps object.
-     */
-    @NonNull
-    public AllAppsFromOverview getAllAppsFromOverview() {
-        try (LauncherInstrumentation.Closable c = addContextLayer("want to get all apps object")) {
-            return new AllAppsFromOverview(this);
+            return new HomeAllApps(this);
         }
     }
 
@@ -1117,15 +1107,21 @@
         }
     }
 
-    @NonNull
-    UiObject2 waitForObjectInContainer(UiObject2 container, BySelector selector) {
+    @NonNull UiObject2 waitForObjectInContainer(UiObject2 container, BySelector selector) {
+        return waitForObjectsInContainer(container, selector).get(0);
+    }
+
+    @NonNull List<UiObject2> waitForObjectsInContainer(
+            UiObject2 container, BySelector selector) {
         try {
-            final UiObject2 object = container.wait(
-                    Until.findObject(selector),
+            final List<UiObject2> objects = container.wait(
+                    Until.findObjects(selector),
                     WAIT_TIME_MS);
-            assertNotNull("Can't find a view in Launcher, id: " + selector + " in container: "
-                    + container.getResourceName(), object);
-            return object;
+            assertNotNull("Can't find views in Launcher, id: " + selector + " in container: "
+                    + container.getResourceName(), objects);
+            assertTrue("Can't find views in Launcher, id: " + selector + " in container: "
+                    + container.getResourceName(), objects.size() > 0);
+            return objects;
         } catch (StaleObjectException e) {
             fail("The container disappeared from screen");
             return null;
diff --git a/tests/tapl/com/android/launcher3/tapl/Overview.java b/tests/tapl/com/android/launcher3/tapl/Overview.java
index 0d06be3..66a51a5 100644
--- a/tests/tapl/com/android/launcher3/tapl/Overview.java
+++ b/tests/tapl/com/android/launcher3/tapl/Overview.java
@@ -16,12 +16,7 @@
 
 package com.android.launcher3.tapl;
 
-import static com.android.launcher3.testing.TestProtocol.ALL_APPS_STATE_ORDINAL;
-
-import androidx.annotation.NonNull;
-
 import com.android.launcher3.tapl.LauncherInstrumentation.ContainerType;
-import com.android.launcher3.testing.TestProtocol;
 
 /**
  * Overview pane.
@@ -37,38 +32,6 @@
         return LauncherInstrumentation.ContainerType.OVERVIEW;
     }
 
-    /**
-     * Swipes up to All Apps.
-     *
-     * @return the App Apps object.
-     */
-    @NonNull
-    public AllAppsFromOverview switchToAllApps() {
-        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
-             LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
-                     "want to switch from overview to all apps")) {
-            verifyActiveContainer();
-
-            // Swipe from an app icon to the top.
-            LauncherInstrumentation.log("Overview.switchToAllApps before swipe");
-            mLauncher.swipeToState(
-                    mLauncher.getDevice().getDisplayWidth() / 2,
-                    mLauncher.getTestInfo(
-                            TestProtocol.REQUEST_HOTSEAT_TOP).
-                            getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD),
-                    mLauncher.getDevice().getDisplayWidth() / 2,
-                    0,
-                    12,
-                    ALL_APPS_STATE_ORDINAL,
-                    LauncherInstrumentation.GestureScope.INSIDE);
-
-            try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
-                    "swiped all way up from overview")) {
-                return new AllAppsFromOverview(mLauncher);
-            }
-        }
-    }
-
     @Override
     public void dismissAllTasks() {
         super.dismissAllTasks();
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index a860e7d..c8caa42 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -127,7 +127,7 @@
     /**
      * Clicks at the task.
      */
-    public Background open() {
+    public LaunchedAppState open() {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
             verifyActiveContainer();
             mLauncher.executeAndWaitForEvent(
@@ -137,7 +137,7 @@
                             + mTask.getParent().getContentDescription(),
                     "clicking an overview task");
             mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, TASK_START_EVENT);
-            return new Background(mLauncher);
+            return new LaunchedAppState(mLauncher);
         }
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/Widget.java b/tests/tapl/com/android/launcher3/tapl/Widget.java
index f569ef4..73e9830 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widget.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widget.java
@@ -30,7 +30,7 @@
 /**
  * Widget in workspace or a widget list.
  */
-public final class Widget extends Launchable {
+public final class Widget extends Launchable implements WorkspaceDragSource {
 
     private static final Pattern LONG_CLICK_EVENT = Pattern.compile("Widgets.onLongClick");
 
@@ -57,6 +57,12 @@
         return "widget";
     }
 
+    /** This method requires public access, however should not be called in tests. */
+    @Override
+    public Launchable getLaunchable() {
+        return this;
+    }
+
     /**
      * Drags a non-configurable widget from the widgets container to the workspace and returns the
      * resize frame that is shown after the widget is added.
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 20d860d..4f4ce30 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -20,7 +20,6 @@
 
 import static com.android.launcher3.testing.TestProtocol.ALL_APPS_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.NORMAL_STATE_ORDINAL;
-import static com.android.launcher3.testing.TestProtocol.SPRING_LOADED_STATE_ORDINAL;
 
 import static junit.framework.TestCase.assertNotNull;
 import static junit.framework.TestCase.assertTrue;
@@ -79,7 +78,7 @@
      * @return the All Apps object.
      */
     @NonNull
-    public AllApps switchToAllApps() {
+    public HomeAllApps switchToAllApps() {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
              LauncherInstrumentation.Closable c =
                      mLauncher.addContextLayer("want to switch from workspace to all apps")) {
@@ -106,7 +105,7 @@
 
             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
                     "swiped to all apps")) {
-                return new AllApps(mLauncher);
+                return new HomeAllApps(mLauncher);
             }
         }
     }
@@ -118,7 +117,7 @@
      * @return app icon, if found, null otherwise.
      */
     @Nullable
-    public AppIcon tryGetWorkspaceAppIcon(String appName) {
+    public HomeAppIcon tryGetWorkspaceAppIcon(String appName) {
         try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                 "want to get a workspace icon")) {
             final UiObject2 workspace = verifyActiveContainer();
@@ -136,7 +135,7 @@
      * @return app icon.
      */
     @NonNull
-    public AppIcon getWorkspaceAppIcon(String appName) {
+    public HomeAppIcon getWorkspaceAppIcon(String appName) {
         try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                 "want to get a workspace icon")) {
             return new WorkspaceAppIcon(mLauncher,
@@ -179,12 +178,12 @@
      * pageDelta: 3, the latest page that can be created is 2) the icon will be dragged onto the
      * page that can be created and is closest to the target page.
      *
-     * @param appIcon   - icon to drag.
+     * @param homeAppIcon   - icon to drag.
      * @param pageDelta - how many pages should the icon be dragged from the current page.
      *                  It can be a negative value. currentPage + pageDelta should be greater
      *                  than or equal to 0.
      */
-    public void dragIcon(AppIcon appIcon, int pageDelta) {
+    public void dragIcon(HomeAppIcon homeAppIcon, int pageDelta) {
         if (mHotseat.getVisibleBounds().height() > mHotseat.getVisibleBounds().width()) {
             throw new UnsupportedOperationException(
                     "dragIcon does NOT support dragging when the hotseat is on the side.");
@@ -193,18 +192,18 @@
             final UiObject2 workspace = verifyActiveContainer();
             try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                     "dragging icon to page with delta: " + pageDelta)) {
-                dragIcon(workspace, appIcon, pageDelta);
+                dragIcon(workspace, homeAppIcon, pageDelta);
                 verifyActiveContainer();
             }
         }
     }
 
-    private void dragIcon(UiObject2 workspace, AppIcon appIcon, int pageDelta) {
+    private void dragIcon(UiObject2 workspace, HomeAppIcon homeAppIcon, int pageDelta) {
         int pageWidth = mLauncher.getDevice().getDisplayWidth() / pagesPerScreen();
         int targetX = (pageWidth / 2) + pageWidth * pageDelta;
         dragIconToWorkspace(
                 mLauncher,
-                appIcon,
+                homeAppIcon,
                 new Point(targetX, mLauncher.getVisibleBounds(workspace).centerY()),
                 "popup_container",
                 false,
@@ -219,16 +218,11 @@
     }
 
     @NonNull
-    public AppIcon getHotseatAppIcon(String appName) {
+    public HomeAppIcon getHotseatAppIcon(String appName) {
         return new WorkspaceAppIcon(mLauncher, mLauncher.waitForObjectInContainer(
                 mHotseat, AppIcon.getAppIconSelector(appName, mLauncher)));
     }
 
-    private static int getStartDragThreshold(LauncherInstrumentation launcher) {
-        return launcher.getTestInfo(TestProtocol.REQUEST_START_DRAG_THRESHOLD).getInt(
-                TestProtocol.TEST_INFO_RESPONSE_FIELD);
-    }
-
     /*
      * Get the center point of the delete/uninstall icon in the drop target bar.
      */
@@ -242,18 +236,18 @@
     /**
      * Delete the appIcon from the workspace.
      *
-     * @param appIcon to be deleted.
+     * @param homeAppIcon to be deleted.
      * @return validated workspace after the existing appIcon being deleted.
      */
-    public Workspace deleteAppIcon(AppIcon appIcon) {
+    public Workspace deleteAppIcon(HomeAppIcon homeAppIcon) {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                      "removing app icon from workspace")) {
             dragIconToWorkspace(
-                    mLauncher, appIcon,
+                    mLauncher, homeAppIcon,
                     () -> getDropPointFromDropTargetBar(mLauncher, DELETE_TARGET_TEXT_ID),
                     true, /* decelerating */
-                    appIcon.getLongPressIndicator(),
+                    homeAppIcon.getLongPressIndicator(),
                     () -> mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, LONG_CLICK_EVENT),
                     null);
 
@@ -267,21 +261,23 @@
     /**
      * Uninstall the appIcon by dragging it to the 'uninstall' drop point of the drop_target_bar.
      *
+     * @param launcher the root TAPL instrumentation object of {@link LauncherInstrumentation} type.
+     * @param homeAppIcon to be uninstalled.
      * @param launcher              the root TAPL instrumentation object of {@link
      *                              LauncherInstrumentation} type.
-     * @param appIcon               to be uninstalled.
+     * @param homeAppIcon           to be uninstalled.
      * @param expectLongClickEvents the runnable to be executed to verify expected longclick event.
      * @return validated workspace after the existing appIcon being uninstalled.
      */
-    static Workspace uninstallAppIcon(LauncherInstrumentation launcher, AppIcon appIcon,
+    static Workspace uninstallAppIcon(LauncherInstrumentation launcher, HomeAppIcon homeAppIcon,
             Runnable expectLongClickEvents) {
         try (LauncherInstrumentation.Closable c = launcher.addContextLayer(
                 "uninstalling app icon")) {
             dragIconToWorkspace(
-                    launcher, appIcon,
+                    launcher, homeAppIcon,
                     () -> getDropPointFromDropTargetBar(launcher, UNINSTALL_TARGET_TEXT_ID),
                     true, /* decelerating */
-                    appIcon.getLongPressIndicator(),
+                    homeAppIcon.getLongPressIndicator(),
                     expectLongClickEvents,
                     null);
 
@@ -334,31 +330,6 @@
                 o -> new FolderIcon(mLauncher, o)).collect(Collectors.toList());
     }
 
-    /**
-     * Drag an icon up with a short distance that makes workspace go to spring loaded state.
-     *
-     * @return the position after dragging.
-     */
-    private static Point dragIconToSpringLoaded(LauncherInstrumentation launcher, long downTime,
-            UiObject2 icon,
-            String longPressIndicator, Runnable expectLongClickEvents) {
-        final Point iconCenter = icon.getVisibleCenter();
-        final Point dragStartCenter = new Point(iconCenter.x,
-                iconCenter.y - getStartDragThreshold(launcher));
-
-        launcher.runToState(() -> {
-            launcher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN,
-                    iconCenter, LauncherInstrumentation.GestureScope.INSIDE);
-            LauncherInstrumentation.log("dragIconToSpringLoaded: sent down");
-            expectLongClickEvents.run();
-            launcher.waitForLauncherObject(longPressIndicator);
-            LauncherInstrumentation.log("dragIconToSpringLoaded: indicator");
-            launcher.movePointer(iconCenter, dragStartCenter, DEFAULT_DRAG_STEPS, false,
-                    downTime, true, LauncherInstrumentation.GestureScope.INSIDE);
-        }, SPRING_LOADED_STATE_ORDINAL, "long-pressing and triggering drag start");
-        return dragStartCenter;
-    }
-
     private static void dropDraggedIcon(LauncherInstrumentation launcher, Point dest, long downTime,
             @Nullable Runnable expectedEvents) {
         launcher.runToState(
@@ -403,8 +374,11 @@
         try (LauncherInstrumentation.Closable ignored = launcher.addContextLayer(
                 "want to drag icon to workspace")) {
             final long downTime = SystemClock.uptimeMillis();
-            Point dragStart = dragIconToSpringLoaded(launcher, downTime,
-                    launchable.getObject(), longPressIndicator, expectLongClickEvents);
+            Point dragStart = launchable.startDrag(
+                    downTime,
+                    longPressIndicator,
+                    expectLongClickEvents,
+                    /* runToSpringLoadedState= */ true);
             Point targetDest = dest.get();
             int displayX = launcher.getRealDisplaySize().x;
 
diff --git a/tests/tapl/com/android/launcher3/tapl/WorkspaceAppIcon.java b/tests/tapl/com/android/launcher3/tapl/WorkspaceAppIcon.java
index 0523a63..114e6a5 100644
--- a/tests/tapl/com/android/launcher3/tapl/WorkspaceAppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/WorkspaceAppIcon.java
@@ -25,7 +25,7 @@
 /**
  * App icon in workspace.
  */
-final class WorkspaceAppIcon extends AppIcon {
+final class WorkspaceAppIcon extends HomeAppIcon {
 
     WorkspaceAppIcon(LauncherInstrumentation launcher, UiObject2 icon) {
         super(launcher, icon);
diff --git a/tests/tapl/com/android/launcher3/tapl/WorkspaceDragSource.java b/tests/tapl/com/android/launcher3/tapl/WorkspaceDragSource.java
new file mode 100644
index 0000000..93c2213
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/WorkspaceDragSource.java
@@ -0,0 +1,53 @@
+/*
+ * 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 android.graphics.Point;
+
+/** Launchable that can serve as a source for dragging and dropping to the workspace. */
+interface WorkspaceDragSource {
+
+    /**
+     * Drags an object to the center of homescreen.
+     *
+     * @param startsActivity   whether it's expected to start an activity.
+     * @param isWidgetShortcut whether we drag a widget shortcut
+     */
+    default void dragToWorkspace(boolean startsActivity, boolean isWidgetShortcut) {
+        Launchable launchable = getLaunchable();
+        LauncherInstrumentation launcher = launchable.mLauncher;
+        try (LauncherInstrumentation.Closable e = launcher.eventsCheck()) {
+            final Point launchableCenter = launchable.getObject().getVisibleCenter();
+            final Point displaySize = launcher.getRealDisplaySize();
+            final int width = displaySize.x / 2;
+            Workspace.dragIconToWorkspace(
+                    launcher,
+                    launchable,
+                    new Point(
+                            launchableCenter.x >= width
+                                    ? launchableCenter.x - width / 2
+                                    : launchableCenter.x + width / 2,
+                            displaySize.y / 2),
+                    launchable.getLongPressIndicator(),
+                    startsActivity,
+                    isWidgetShortcut,
+                    launchable::addExpectedEventsForLongClick);
+        }
+    }
+
+    /** This method requires public access, however should not be called in tests. */
+    Launchable getLaunchable();
+}