Merge "Revert "Clear all DragView in onSaveInstanceState""
diff --git a/Android.bp b/Android.bp
index 7c78ba8..f5a38b4 100644
--- a/Android.bp
+++ b/Android.bp
@@ -36,6 +36,7 @@
         "tests/tapl/**/*.java",
         "src/com/android/launcher3/ResourceUtils.java",
         "src/com/android/launcher3/testing/TestProtocol.java",
+        "src/com/android/launcher3/testing/*Request.java",
     ],
     resource_dirs: [ ],
     manifest: "tests/tapl/AndroidManifest.xml",
diff --git a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
index 0f61d14..02206c0 100644
--- a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
+++ b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
@@ -27,6 +27,7 @@
 import android.view.View;
 
 import androidx.annotation.Keep;
+import androidx.annotation.Nullable;
 
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
@@ -124,7 +125,7 @@
     }
 
     @Override
-    public Bundle call(String method, String arg) {
+    public Bundle call(String method, String arg, @Nullable Bundle extras) {
         final Bundle response = new Bundle();
         switch (method) {
             case TestProtocol.REQUEST_APP_LIST_FREEZE_FLAGS: {
@@ -219,7 +220,7 @@
             }
 
             default:
-                return super.call(method, arg);
+                return super.call(method, arg, extras);
         }
     }
 }
diff --git a/quickstep/res/layout/taskbar_all_apps.xml b/quickstep/res/layout/taskbar_all_apps.xml
index 1ad20cc..11d75c7 100644
--- a/quickstep/res/layout/taskbar_all_apps.xml
+++ b/quickstep/res/layout/taskbar_all_apps.xml
@@ -46,6 +46,8 @@
             android:paddingTop="@dimen/all_apps_header_top_padding"
             android:orientation="vertical">
 
+            <include layout="@layout/floating_header_content" />
+
             <include layout="@layout/all_apps_personal_work_tabs" />
         </com.android.launcher3.allapps.FloatingHeaderView>
 
diff --git a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
index 4f987d2..d37e530 100644
--- a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
@@ -16,7 +16,7 @@
 
 package com.android.launcher3.appprediction;
 
-import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.util.OnboardingPrefs.ALL_APPS_VISITED_COUNT;
 
 import android.annotation.TargetApi;
 import android.content.Context;
@@ -34,23 +34,17 @@
 import androidx.core.content.ContextCompat;
 
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.FloatingHeaderRow;
 import com.android.launcher3.allapps.FloatingHeaderView;
-import com.android.launcher3.statemanager.StateManager.StateListener;
 import com.android.launcher3.util.Themes;
+import com.android.launcher3.views.ActivityContext;
 
 /**
  * A view which shows a horizontal divider
  */
 @TargetApi(Build.VERSION_CODES.O)
-public class AppsDividerView extends View implements StateListener<LauncherState>,
-        FloatingHeaderRow {
-
-    private static final String ALL_APPS_VISITED_COUNT = "launcher.all_apps_visited_count";
-    private static final int SHOW_ALL_APPS_LABEL_ON_ALL_APPS_VISITED_COUNT = 20;
+public class AppsDividerView extends View implements FloatingHeaderRow {
 
     public enum DividerType {
         NONE,
@@ -58,7 +52,6 @@
         ALL_APPS_LABEL
     }
 
-    private final Launcher mLauncher;
     private final TextPaint mPaint = new TextPaint();
     private DividerType mDividerType = DividerType.NONE;
 
@@ -86,7 +79,6 @@
 
     public AppsDividerView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
-        mLauncher = Launcher.getLauncher(context);
 
         boolean isMainColorDark = Themes.getAttrBoolean(context, R.attr.isMainColorDark);
         mDividerSize = new int[]{
@@ -101,6 +93,9 @@
         mAllAppsLabelTextColor = ContextCompat.getColor(context, isMainColorDark
                 ? R.color.all_apps_label_text_dark
                 : R.color.all_apps_label_text);
+
+        mShowAllAppsLabel = !ActivityContext.lookupContext(
+                getContext()).getOnboardingPrefs().hasReachedMaxCount(ALL_APPS_VISITED_COUNT);
     }
 
     public void setup(FloatingHeaderView parent, FloatingHeaderRow[] rows, boolean tabsHidden) {
@@ -110,6 +105,14 @@
         updateDividerType();
     }
 
+    /** {@code true} if all apps label should be shown in place of divider. */
+    public void setShowAllAppsLabel(boolean showAllAppsLabel) {
+        if (showAllAppsLabel != mShowAllAppsLabel) {
+            mShowAllAppsLabel = showAllAppsLabel;
+            updateDividerType();
+        }
+    }
+
     @Override
     public int getExpectedHeight() {
         return getPaddingTop() + getPaddingBottom();
@@ -236,51 +239,6 @@
     }
 
     @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-
-        if (shouldShowAllAppsLabel()) {
-            mShowAllAppsLabel = true;
-            mLauncher.getStateManager().addStateListener(this);
-            updateDividerType();
-        }
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        mLauncher.getStateManager().removeStateListener(this);
-    }
-
-    @Override
-    public void onStateTransitionComplete(LauncherState finalState) {
-        if (finalState == ALL_APPS) {
-            setAllAppsVisitedCount(getAllAppsVisitedCount() + 1);
-        } else {
-            if (mShowAllAppsLabel != shouldShowAllAppsLabel()) {
-                mShowAllAppsLabel = !mShowAllAppsLabel;
-                updateDividerType();
-            }
-
-            if (!mShowAllAppsLabel) {
-                mLauncher.getStateManager().removeStateListener(this);
-            }
-        }
-    }
-
-    private void setAllAppsVisitedCount(int count) {
-        mLauncher.getSharedPrefs().edit().putInt(ALL_APPS_VISITED_COUNT, count).apply();
-    }
-
-    private int getAllAppsVisitedCount() {
-        return mLauncher.getSharedPrefs().getInt(ALL_APPS_VISITED_COUNT, 0);
-    }
-
-    private boolean shouldShowAllAppsLabel() {
-        return getAllAppsVisitedCount() < SHOW_ALL_APPS_LABEL_ON_ALL_APPS_VISITED_COUNT;
-    }
-
-    @Override
     public void setInsets(Rect insets, DeviceProfile grid) {
         int leftRightPadding = grid.allAppsLeftRightPadding;
         setPadding(leftRightPadding, getPaddingTop(), leftRightPadding, getPaddingBottom());
diff --git a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
index 9ad8bb2..1dec737 100644
--- a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -31,8 +31,8 @@
 
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.DeviceProfile.DeviceProfileListenable;
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.FloatingHeaderRow;
 import com.android.launcher3.allapps.FloatingHeaderView;
@@ -43,18 +43,18 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.touch.ItemLongClickListener;
+import com.android.launcher3.views.ActivityContext;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.stream.Collectors;
 
 @TargetApi(Build.VERSION_CODES.P)
-public class PredictionRowView extends LinearLayout implements
-        OnDeviceProfileChangeListener, FloatingHeaderRow {
+public class PredictionRowView<T extends Context & ActivityContext & DeviceProfileListenable>
+        extends LinearLayout implements OnDeviceProfileChangeListener, FloatingHeaderRow {
 
-    private final Launcher mLauncher;
+    private final T mActivityContext;
     private int mNumPredictedAppsPerRow;
 
     // Helper to drawing the focus indicator.
@@ -64,12 +64,10 @@
     private final List<WorkspaceItemInfo> mPredictedApps = new ArrayList<>();
 
     private FloatingHeaderView mParent;
-    private boolean mScrolledOut;
 
     private boolean mPredictionsEnabled = false;
-
-    @Nullable
-    private List<ItemInfo> mPendingPredictedItems;
+    private @Nullable List<ItemInfo> mPendingPredictedItems;
+    private OnLongClickListener mOnIconLongClickListener = ItemLongClickListener.INSTANCE_ALL_APPS;
 
     public PredictionRowView(@NonNull Context context) {
         this(context, null);
@@ -80,9 +78,9 @@
         setOrientation(LinearLayout.HORIZONTAL);
 
         mFocusHelper = new SimpleFocusIndicatorHelper(this);
-        mLauncher = Launcher.getLauncher(context);
-        mLauncher.addOnDeviceProfileChangeListener(this);
-        mNumPredictedAppsPerRow = mLauncher.getDeviceProfile().numShownAllAppsColumns;
+        mActivityContext = ActivityContext.lookupContext(context);
+        mActivityContext.addOnDeviceProfileChangeListener(this);
+        mNumPredictedAppsPerRow = mActivityContext.getDeviceProfile().numShownAllAppsColumns;
         updateVisibility();
     }
 
@@ -97,11 +95,11 @@
 
     private void updateVisibility() {
         setVisibility(mPredictionsEnabled ? VISIBLE : GONE);
-        if (mLauncher.getAppsView() != null) {
+        if (mActivityContext.getAppsView() != null) {
             if (mPredictionsEnabled) {
-                mLauncher.getAppsView().getAppsStore().registerIconContainer(this);
+                mActivityContext.getAppsView().getAppsStore().registerIconContainer(this);
             } else {
-                mLauncher.getAppsView().getAppsStore().unregisterIconContainer(this);
+                mActivityContext.getAppsView().getAppsStore().unregisterIconContainer(this);
             }
         }
     }
@@ -120,9 +118,9 @@
 
     @Override
     public int getExpectedHeight() {
-        return getVisibility() == GONE ? 0 :
-                Launcher.getLauncher(getContext()).getDeviceProfile().allAppsCellHeightPx
-                        + getPaddingTop() + getPaddingBottom();
+        return getVisibility() == GONE ? 0
+                : mActivityContext.getDeviceProfile().allAppsCellHeightPx + getPaddingTop()
+                        + getPaddingBottom();
     }
 
     @Override
@@ -158,13 +156,12 @@
      */
     public void setPredictedApps(List<ItemInfo> items) {
         if (!FeatureFlags.ENABLE_APP_PREDICTIONS_WHILE_VISIBLE.get()
-                && !mLauncher.isWorkspaceLoading()
+                && !mActivityContext.isBindingItems()
                 && isShown()
                 && getWindowVisibility() == View.VISIBLE) {
             mPendingPredictedItems = items;
             return;
         }
-
         applyPredictedApps(items);
     }
 
@@ -177,6 +174,15 @@
         applyPredictionApps();
     }
 
+    /**
+     * Sets the long click listener for predictions for any future predictions.
+     *
+     * Existing predictions in the container are not updated with this new callback.
+     */
+    public void setOnIconLongClickListener(OnLongClickListener onIconLongClickListener) {
+        mOnIconLongClickListener = onIconLongClickListener;
+    }
+
     @Override
     public void onDeviceProfileChanged(DeviceProfile dp) {
         mNumPredictedAppsPerRow = dp.numShownAllAppsColumns;
@@ -189,18 +195,18 @@
             while (getChildCount() > mNumPredictedAppsPerRow) {
                 removeViewAt(0);
             }
-            LayoutInflater inflater = mLauncher.getAppsView().getLayoutInflater();
+            LayoutInflater inflater = mActivityContext.getAppsView().getLayoutInflater();
             while (getChildCount() < mNumPredictedAppsPerRow) {
                 BubbleTextView icon = (BubbleTextView) inflater.inflate(
                         R.layout.all_apps_icon, this, false);
-                icon.setOnClickListener(ItemClickHandler.INSTANCE);
-                icon.setOnLongClickListener(ItemLongClickListener.INSTANCE_ALL_APPS);
+                icon.setOnClickListener(mActivityContext.getItemOnClickListener());
+                icon.setOnLongClickListener(mOnIconLongClickListener);
                 icon.setLongPressTimeoutFactor(1f);
                 icon.setOnFocusChangeListener(mFocusHelper);
 
                 LayoutParams lp = (LayoutParams) icon.getLayoutParams();
                 // Ensure the all apps icon height matches the workspace icons in portrait mode.
-                lp.height = mLauncher.getDeviceProfile().allAppsCellHeightPx;
+                lp.height = mActivityContext.getDeviceProfile().allAppsCellHeightPx;
                 lp.width = 0;
                 lp.weight = 1;
                 addView(icon);
@@ -223,7 +229,6 @@
         boolean predictionsEnabled = predictionCount > 0;
         if (predictionsEnabled != mPredictionsEnabled) {
             mPredictionsEnabled = predictionsEnabled;
-            mLauncher.reapplyUi(false /* cancelCurrentAnimation */);
             updateVisibility();
         }
         mParent.onHeightUpdated();
@@ -237,11 +242,10 @@
 
     @Override
     public void setVerticalScroll(int scroll, boolean isScrolledOut) {
-        mScrolledOut = isScrolledOut;
         if (!isScrolledOut) {
             setTranslationY(scroll);
         }
-        setAlpha(mScrolledOut ? 0 : 1);
+        setAlpha(isScrolledOut ? 0 : 1);
         if (getVisibility() != GONE) {
             AlphaUpdateListener.updateVisibility(this);
         }
@@ -263,6 +267,7 @@
         return getChildAt(0);
     }
 
+
     @Override
     public void onVisibilityAggregated(boolean isVisible) {
         super.onVisibilityAggregated(isVisible);
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 650d4ce..7ab59b8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -176,7 +176,7 @@
 
         boolean isThreeButtonNav = mContext.isThreeButtonNav();
         mIsImeRenderingNavButtons =
-                InputMethodService.canImeRenderGesturalNavButtons() && mContext.isGestureNav();
+                InputMethodService.canImeRenderGesturalNavButtons() && mContext.imeDrawsImeNavBar();
         if (!mIsImeRenderingNavButtons) {
             // IME switcher
             View imeSwitcherButton = addButton(R.drawable.ic_ime_switcher, BUTTON_IME_SWITCH,
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index abe8aad..fe091ef 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -58,8 +58,11 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.DeviceProfile.DeviceProfileListenable;
+import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.dot.DotInfo;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
@@ -70,6 +73,7 @@
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.popup.PopupDataProvider;
 import com.android.launcher3.touch.ItemClickHandler;
+import com.android.launcher3.util.OnboardingPrefs;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.SettingsCache;
 import com.android.launcher3.util.Themes;
@@ -85,13 +89,16 @@
 import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * The {@link ActivityContext} with which we inflate Taskbar-related Views. This allows UI elements
  * that are used by both Launcher and Taskbar (such as Folder) to reference a generic
  * ActivityContext and BaseDragLayer instead of the Launcher activity and its DragLayer.
  */
-public class TaskbarActivityContext extends ContextThemeWrapper implements ActivityContext {
+public class TaskbarActivityContext extends ContextThemeWrapper implements ActivityContext,
+        DeviceProfileListenable {
 
     private static final boolean ENABLE_THREE_BUTTON_TASKBAR =
             SystemProperties.getBoolean("persist.debug.taskbar_three_button", false);
@@ -103,6 +110,7 @@
     private final TaskbarDragLayer mDragLayer;
     private final TaskbarAllAppsContainerView mAppsView;
     private final TaskbarControllers mControllers;
+    private final List<OnDeviceProfileChangeListener> mDPChangeListeners = new ArrayList<>();
 
     private DeviceProfile mDeviceProfile;
 
@@ -115,6 +123,7 @@
     private int mLastRequestedNonFullscreenHeight;
 
     private final SysUINavigationMode.Mode mNavMode;
+    private final boolean mImeDrawsImeNavBar;
     private final ViewCache mViewCache = new ViewCache();
 
     private final boolean mIsSafeModeEnabled;
@@ -123,16 +132,20 @@
     private boolean mIsDestroyed = false;
     // The flag to know if the window is excluded from magnification region computation.
     private boolean mIsExcludeFromMagnificationRegion = false;
+    private boolean mBindingItems = false;
 
     private final TaskbarShortcutMenuAccessibilityDelegate mAccessibilityDelegate;
+    private final OnboardingPrefs<TaskbarActivityContext> mOnboardingPrefs;
 
     public TaskbarActivityContext(Context windowContext, DeviceProfile dp,
             TaskbarNavButtonController buttonController, ScopedUnfoldTransitionProgressProvider
             unfoldTransitionProgressProvider) {
         super(windowContext, Themes.getActivityThemeRes(windowContext));
         mDeviceProfile = dp;
+        mOnboardingPrefs = new OnboardingPrefs<>(this, Utilities.getPrefs(this));
 
         mNavMode = SysUINavigationMode.getMode(windowContext);
+        mImeDrawsImeNavBar = SysUINavigationMode.getImeDrawsImeNavBar(windowContext);
         mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
                 () -> getPackageManager().isSafeMode());
         mIsUserSetupComplete = SettingsCache.INSTANCE.get(this).getValue(
@@ -227,6 +240,7 @@
     public void updateDeviceProfile(DeviceProfile dp) {
         mDeviceProfile = dp;
         updateIconSize(getResources());
+        dispatchDeviceProfileChanged();
     }
 
     private void updateIconSize(Resources resources) {
@@ -270,6 +284,10 @@
         return mNavMode == Mode.NO_BUTTON;
     }
 
+    public boolean imeDrawsImeNavBar() {
+        return mImeDrawsImeNavBar;
+    }
+
     public int getLeftCornerRadius() {
         return mLeftCorner == null ? 0 : mLeftCorner.getRadius();
     }
@@ -299,6 +317,11 @@
     }
 
     @Override
+    public List<OnDeviceProfileChangeListener> getOnDeviceProfileChangeListeners() {
+        return mDPChangeListeners;
+    }
+
+    @Override
     public Rect getFolderBoundingBox() {
         return mControllers.taskbarDragLayerController.getFolderBoundingBox();
     }
@@ -388,6 +411,20 @@
         return mAccessibilityDelegate;
     }
 
+    @Override
+    public OnboardingPrefs<TaskbarActivityContext> getOnboardingPrefs() {
+        return mOnboardingPrefs;
+    }
+
+    @Override
+    public boolean isBindingItems() {
+        return mBindingItems;
+    }
+
+    public void setBindingItems(boolean bindingItems) {
+        mBindingItems = bindingItems;
+    }
+
     /**
      * Sets a new data-source for this taskbar instance
      */
@@ -727,9 +764,13 @@
         pw.println(String.format(
                 "%s\tmNavMode=%s", prefix, mNavMode));
         pw.println(String.format(
+                "%s\tmImeDrawsImeNavBar=%b", prefix, mImeDrawsImeNavBar));
+        pw.println(String.format(
                 "%s\tmIsUserSetupComplete=%b", prefix, mIsUserSetupComplete));
         pw.println(String.format(
                 "%s\tmWindowLayoutParams.height=%dpx", prefix, mWindowLayoutParams.height));
+        pw.println(String.format(
+                "%s\tmBindInProgress=%b", prefix, mBindingItems));
         mControllers.dumpLogs(prefix + "\t", pw);
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarAllAppsViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarAllAppsViewController.java
index 62125fe..2670200 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarAllAppsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarAllAppsViewController.java
@@ -15,8 +15,15 @@
  */
 package com.android.launcher3.taskbar;
 
+import static com.android.launcher3.util.OnboardingPrefs.ALL_APPS_VISITED_COUNT;
+
+import com.android.launcher3.appprediction.AppsDividerView;
+import com.android.launcher3.appprediction.PredictionRowView;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.ItemInfo;
+
+import java.util.List;
 
 /** Handles the {@link TaskbarAllAppsContainerView} initialization and updates. */
 public final class TaskbarAllAppsViewController {
@@ -40,6 +47,9 @@
 
         mAppsView.setOnIconLongClickListener(
                 controllers.taskbarDragController::startDragOnLongClick);
+        mAppsView.getFloatingHeaderView().findFixedRowByType(
+                PredictionRowView.class).setOnIconLongClickListener(
+                controllers.taskbarDragController::startDragOnLongClick);
     }
 
     /** Binds the current {@link AppInfo} instances to the {@link TaskbarAllAppsContainerView}. */
@@ -49,11 +59,26 @@
         }
     }
 
+    /** Binds the current app predictions to all apps {@link PredictionRowView}. */
+    public void setPredictedApps(List<ItemInfo> predictedApps) {
+        if (FeatureFlags.ENABLE_ALL_APPS_IN_TASKBAR.get()) {
+            PredictionRowView<?> predictionRowView =
+                    mAppsView.getFloatingHeaderView().findFixedRowByType(PredictionRowView.class);
+            predictionRowView.setPredictedApps(predictedApps);
+        }
+    }
+
     /** Opens the {@link TaskbarAllAppsContainerView}. */
     public void show() {
-        if (FeatureFlags.ENABLE_ALL_APPS_IN_TASKBAR.get()) {
-            mContext.setTaskbarWindowFullscreen(true);
-            mSlideInView.show();
+        if (!FeatureFlags.ENABLE_ALL_APPS_IN_TASKBAR.get()) {
+            return;
         }
+
+        mAppsView.getFloatingHeaderView().findFixedRowByType(AppsDividerView.class)
+                .setShowAllAppsLabel(
+                        !mContext.getOnboardingPrefs().hasReachedMaxCount(ALL_APPS_VISITED_COUNT));
+        mContext.getOnboardingPrefs().incrementEventCount(ALL_APPS_VISITED_COUNT);
+        mContext.setTaskbarWindowFullscreen(true);
+        mSlideInView.show();
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
index 2bafccd..2e18a40 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
@@ -52,8 +52,6 @@
     // Initialized in init.
     private TaskbarControllers mControllers;
 
-    private boolean mBindInProgress = false;
-
     public TaskbarModelCallbacks(
             TaskbarActivityContext context, TaskbarView container) {
         mContext = context;
@@ -66,14 +64,14 @@
 
     @Override
     public void startBinding() {
-        mBindInProgress = true;
+        mContext.setBindingItems(true);
         mHotseatItems.clear();
         mPredictedItems = Collections.emptyList();
     }
 
     @Override
     public void finishBindingItems(IntSet pagesBoundFirst) {
-        mBindInProgress = false;
+        mContext.setBindingItems(false);
         commitItemsToUI();
     }
 
@@ -159,11 +157,13 @@
         if (item.containerId == Favorites.CONTAINER_HOTSEAT_PREDICTION) {
             mPredictedItems = item.items;
             commitItemsToUI();
+        } else if (item.containerId == Favorites.CONTAINER_PREDICTION) {
+            mControllers.taskbarAllAppsViewController.setPredictedApps(item.items);
         }
     }
 
     private void commitItemsToUI() {
-        if (mBindInProgress) {
+        if (mContext.isBindingItems()) {
             return;
         }
 
@@ -212,6 +212,5 @@
             pw.println(
                     String.format("%s\tpredicted items count=%s", prefix, mPredictedItems.size()));
         }
-        pw.println(String.format("%s\tmBindInProgress=%b", prefix, mBindInProgress));
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 9050ddc..1073d76 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -69,7 +69,6 @@
 import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.TwoButtonNavbarTouchController;
 import com.android.launcher3.util.ItemInfoMatcher;
-import com.android.launcher3.util.OnboardingPrefs;
 import com.android.launcher3.util.PendingRequestArgs;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.util.UiThreadHelper;
@@ -162,7 +161,7 @@
     }
 
     @Override
-    protected OnboardingPrefs createOnboardingPrefs(SharedPreferences sharedPrefs) {
+    protected QuickstepOnboardingPrefs createOnboardingPrefs(SharedPreferences sharedPrefs) {
         return new QuickstepOnboardingPrefs(this, sharedPrefs);
     }
 
@@ -235,8 +234,10 @@
     public void bindExtraContainerItems(FixedContainerItems item) {
         if (item.containerId == Favorites.CONTAINER_PREDICTION) {
             mAllAppsPredictions = item;
-            getAppsView().getFloatingHeaderView().findFixedRowByType(PredictionRowView.class)
-                    .setPredictedApps(item.items);
+            PredictionRowView<?> predictionRowView =
+                    getAppsView().getFloatingHeaderView().findFixedRowByType(
+                            PredictionRowView.class);
+            predictionRowView.setPredictedApps(item.items);
         } else if (item.containerId == Favorites.CONTAINER_HOTSEAT_PREDICTION) {
             mHotseatPredictionController.setPredictedItems(item);
         } else if (item.containerId == Favorites.CONTAINER_WIDGETS_PREDICTION) {
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 ae2583b..ef81449 100644
--- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -5,11 +5,11 @@
 import android.graphics.Rect;
 import android.os.Bundle;
 
-import com.android.launcher3.LauncherState;
+import androidx.annotation.Nullable;
+
 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 {
@@ -21,18 +21,9 @@
     }
 
     @Override
-    public Bundle call(String method, String arg) {
+    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);
@@ -48,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;
@@ -82,7 +68,7 @@
             }
         }
 
-        return super.call(method, arg);
+        return super.call(method, arg, extras);
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/SysUINavigationMode.java b/quickstep/src/com/android/quickstep/SysUINavigationMode.java
index 53db4cc..406414c 100644
--- a/quickstep/src/com/android/quickstep/SysUINavigationMode.java
+++ b/quickstep/src/com/android/quickstep/SysUINavigationMode.java
@@ -60,17 +60,22 @@
         return INSTANCE.get(context).getMode();
     }
 
+    public static boolean getImeDrawsImeNavBar(Context context) {
+        return INSTANCE.get(context).getImeDrawsImeNavBar();
+    }
+
     public static final MainThreadInitializedObject<SysUINavigationMode> INSTANCE =
             new MainThreadInitializedObject<>(SysUINavigationMode::new);
 
     private static final String TAG = "SysUINavigationMode";
     private static final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
-    private static final String NAV_BAR_INTERACTION_MODE_RES_NAME =
-            "config_navBarInteractionMode";
+    private static final String NAV_BAR_INTERACTION_MODE_RES_NAME = "config_navBarInteractionMode";
+    private static final String IME_DRAWS_IME_NAV_BAR_RES_NAME = "config_imeDrawsImeNavBar";
     private static final String TARGET_OVERLAY_PACKAGE = "android";
 
     private final Context mContext;
     private Mode mMode;
+    private boolean mImeDrawsImeNavBar;
 
     private int mNavBarGesturalHeight;
     private int mNavBarLargerGesturalHeight;
@@ -135,6 +140,8 @@
         mNavBarLargerGesturalHeight = ResourceUtils.getDimenByName(
                 ResourceUtils.NAVBAR_BOTTOM_GESTURE_LARGER_SIZE, mContext.getResources(),
                 mNavBarGesturalHeight);
+        mImeDrawsImeNavBar = ResourceUtils.getBoolByName(IME_DRAWS_IME_NAV_BAR_RES_NAME,
+                mContext.getResources(), false);
 
         if (modeInt == INVALID_RESOURCE_HANDLE) {
             Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
@@ -167,9 +174,14 @@
         return mMode;
     }
 
+    public boolean getImeDrawsImeNavBar() {
+        return mImeDrawsImeNavBar;
+    }
+
     public void dump(PrintWriter pw) {
         pw.println("SysUINavigationMode:");
         pw.println("  mode=" + mMode.name());
+        pw.println("  mImeDrawsImeNavBar=:" + mImeDrawsImeNavBar);
         pw.println("  mNavBarGesturalHeight=:" + mNavBarGesturalHeight);
     }
 
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 4aa69d1..d28796c 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -609,14 +609,13 @@
     }
 
     public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent,
-            Intent fillInIntent, int taskId, boolean intentFirst, Bundle mainOptions,
-            Bundle sideOptions, @SplitConfigurationOptions.StagePosition int sidePosition,
-            float splitRatio, RemoteAnimationAdapter adapter) {
+            Intent fillInIntent, int taskId, Bundle mainOptions, Bundle sideOptions,
+            @SplitConfigurationOptions.StagePosition int sidePosition, float splitRatio,
+            RemoteAnimationAdapter adapter) {
         if (mSystemUiProxy != null) {
             try {
                 mSplitScreen.startIntentAndTaskWithLegacyTransition(pendingIntent, fillInIntent,
-                        taskId, intentFirst, mainOptions, sideOptions, sidePosition, splitRatio,
-                        adapter);
+                        taskId, mainOptions, sideOptions, sidePosition, splitRatio, adapter);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call startTasksWithLegacyTransition");
             }
diff --git a/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java b/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
index 6b6bd6a..333df10 100644
--- a/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
+++ b/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
@@ -15,9 +15,14 @@
  */
 package com.android.quickstep.util;
 
+import static com.android.launcher3.LauncherAnimUtils.SCALE_INDEX_UNFOLD_ANIMATION;
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY_FACTORY;
 import static com.android.launcher3.Utilities.comp;
 
 import android.annotation.Nullable;
+import android.util.FloatProperty;
+import android.util.MathUtils;
+import android.view.View;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 
@@ -39,6 +44,8 @@
     // Percentage of the width of the quick search bar that will be reduced
     // from the both sides of the bar when progress is 0
     private static final float MAX_WIDTH_INSET_FRACTION = 0.15f;
+    private static final FloatProperty<View> UNFOLD_SCALE_PROPERTY =
+            SCALE_PROPERTY_FACTORY.get(SCALE_INDEX_UNFOLD_ANIMATION);
 
     private final Launcher mLauncher;
 
@@ -62,6 +69,8 @@
         // Animated in all orientations
         mProgressProvider.addCallback(new UnfoldMoveFromCenterWorkspaceAnimator(launcher,
                 windowManager));
+        mProgressProvider
+                .addCallback(new LauncherScaleAnimationListener());
 
         // Animated only in natural orientation
         mNaturalOrientationProgressProvider
@@ -120,4 +129,26 @@
             }
         }
     }
+
+    private class LauncherScaleAnimationListener implements TransitionProgressListener {
+
+        @Override
+        public void onTransitionStarted() {
+        }
+
+        @Override
+        public void onTransitionFinished() {
+            setScale(1);
+        }
+
+        @Override
+        public void onTransitionProgress(float progress) {
+            setScale(MathUtils.constrainedMap(0.85f, 1, 0, 1, progress));
+        }
+
+        private void setScale(float value) {
+            UNFOLD_SCALE_PROPERTY.setValue(mLauncher.getWorkspace(), value);
+            UNFOLD_SCALE_PROPERTY.setValue(mLauncher.getHotseat(), value);
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
index 6d6e802..54642a2 100644
--- a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
+++ b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
@@ -28,6 +28,7 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
+import com.android.launcher3.appprediction.AppsDividerView;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.hybridhotseat.HotseatPredictionController;
 import com.android.launcher3.statemanager.StateManager;
@@ -132,5 +133,24 @@
                 }
             });
         }
+
+        if (!hasReachedMaxCount(ALL_APPS_VISITED_COUNT)) {
+            mLauncher.getStateManager().addStateListener(new StateListener<LauncherState>() {
+                @Override
+                public void onStateTransitionComplete(LauncherState finalState) {
+                    if (finalState == ALL_APPS) {
+                        incrementEventCount(ALL_APPS_VISITED_COUNT);
+                        return;
+                    }
+
+                    boolean hasReachedMaxCount = hasReachedMaxCount(ALL_APPS_VISITED_COUNT);
+                    mLauncher.getAppsView().getFloatingHeaderView().findFixedRowByType(
+                            AppsDividerView.class).setShowAllAppsLabel(!hasReachedMaxCount);
+                    if (hasReachedMaxCount) {
+                        mLauncher.getStateManager().removeStateListener(this);
+                    }
+                }
+            });
+        }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index e856d8a..fff55a1 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -156,9 +156,8 @@
                         splitRatio, adapter);
             } else {
                 mSystemUiProxy.startIntentAndTaskWithLegacyTransition(taskPendingIntent,
-                        new Intent(), taskId2, stagePosition == STAGE_POSITION_TOP_OR_LEFT,
-                        mainOpts.toBundle(), null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT,
-                        splitRatio, adapter);
+                        new Intent(), taskId2, mainOpts.toBundle(), null /* sideOptions */,
+                        stagePosition, splitRatio, adapter);
             }
         }
     }
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
index a534450..ee35adc 100644
--- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -278,7 +278,7 @@
 
     private RotatedPosition getRotatedPosition(float progress) {
         final float degree, positionX, positionY;
-        if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
+        if (TaskAnimationManager.SHELL_TRANSITIONS_ROTATION) {
             if (mFromRotation == Surface.ROTATION_90) {
                 degree = -90 * (1 - progress);
                 positionX = progress * (mDestinationBoundsTransformed.left - mStartBounds.left)
diff --git a/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java b/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java
index 7ae6cb7..8659b68 100644
--- a/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java
+++ b/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java
@@ -15,7 +15,8 @@
  */
 package com.android.quickstep.util;
 
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.LauncherAnimUtils.SCALE_INDEX_REVEAL_ANIM;
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY_FACTORY;
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
@@ -27,6 +28,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
+import android.util.FloatProperty;
 import android.view.View;
 
 import com.android.launcher3.BaseQuickstepLauncher;
@@ -49,6 +51,8 @@
 
     // Should be used for animations running alongside this WorkspaceRevealAnim.
     public static final int DURATION_MS = 350;
+    private static final FloatProperty<View> REVEAL_SCALE_PROPERTY =
+            SCALE_PROPERTY_FACTORY.get(SCALE_INDEX_REVEAL_ANIM);
 
     private final float mScaleStart;
     private final AnimatorSet mAnimators = new AnimatorSet();
@@ -90,7 +94,7 @@
     }
 
     private void addRevealAnimatorsForView(View v) {
-        ObjectAnimator scale = ObjectAnimator.ofFloat(v, SCALE_PROPERTY, mScaleStart, 1f);
+        ObjectAnimator scale = ObjectAnimator.ofFloat(v, REVEAL_SCALE_PROPERTY, mScaleStart, 1f);
         scale.setDuration(DURATION_MS);
         scale.setInterpolator(Interpolators.DECELERATED_EASE);
         mAnimators.play(scale);
@@ -103,7 +107,7 @@
         mAnimators.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
-                SCALE_PROPERTY.set(v, 1f);
+                REVEAL_SCALE_PROPERTY.set(v, 1f);
                 v.setAlpha(1f);
             }
         });
diff --git a/quickstep/src/com/android/quickstep/util/WorkspaceUnlockAnim.java b/quickstep/src/com/android/quickstep/util/WorkspaceUnlockAnim.java
index b01447e..aa3f0d7 100644
--- a/quickstep/src/com/android/quickstep/util/WorkspaceUnlockAnim.java
+++ b/quickstep/src/com/android/quickstep/util/WorkspaceUnlockAnim.java
@@ -21,6 +21,7 @@
 /**
  * Animation to animate in a workspace during the unlock transition.
  */
+// TODO(b/219444608): use SCALE_PROPERTY_FACTORY once the scale is reset to 1.0 after unlocking.
 public class WorkspaceUnlockAnim {
     /** Scale for the workspace icons at the beginning of the animation. */
     private static final float START_SCALE = 0.9f;
diff --git a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
index 9ae5d25..307c51f 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
@@ -30,9 +30,11 @@
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.quickstep.util.MultiValueUpdateListener;
 
+import java.util.function.Consumer;
+
 /**
  * Create an instance via
- * {@link #getFloatingTaskView(StatefulActivity, View, Bitmap, Drawable, RectF)} to
+ * {@link #getFloatingTaskView(StatefulActivity, View, Bitmap, Drawable, RectF, Consumer)} to
  * which will have the thumbnail from the provided existing TaskView overlaying the taskview itself.
  *
  * Can then animate the taskview using
@@ -46,6 +48,8 @@
 
     private SplitPlaceholderView mSplitPlaceholderView;
     private RectF mStartingPosition;
+    @Nullable
+    private Consumer<RectF> mAdditionalOffsetter;
     private final StatefulActivity mActivity;
     private final boolean mIsRtl;
     private final Rect mOutline = new Rect();
@@ -77,8 +81,9 @@
     }
 
     private void init(StatefulActivity launcher, View originalView, @Nullable Bitmap thumbnail,
-            Drawable icon, RectF positionOut) {
+            Drawable icon, RectF positionOut, Consumer<RectF> additionalOffsetter) {
         mStartingPosition = positionOut;
+        mAdditionalOffsetter = additionalOffsetter;
         updateInitialPositionForView(originalView);
         final InsettableFrameLayout.LayoutParams lp =
                 (InsettableFrameLayout.LayoutParams) getLayoutParams();
@@ -102,15 +107,21 @@
     /**
      * Configures and returns a an instance of {@link FloatingTaskView} initially matching the
      * appearance of {@code originalView}.
+     *
+     * @param additionalOffsetter optional, to set additional offsets to the FloatingTaskView
+     *                               to account for translations. If {@code null} then the
+     *                               translation values from originalView will be used
      */
     public static FloatingTaskView getFloatingTaskView(StatefulActivity launcher,
-            View originalView, @Nullable Bitmap thumbnail, Drawable icon, RectF positionOut) {
+            View originalView, @Nullable Bitmap thumbnail, Drawable icon, RectF positionOut,
+            @Nullable Consumer<RectF> additionalOffsetter) {
         final BaseDragLayer dragLayer = launcher.getDragLayer();
         ViewGroup parent = (ViewGroup) dragLayer.getParent();
         final FloatingTaskView floatingView = (FloatingTaskView) launcher.getLayoutInflater()
                 .inflate(R.layout.floating_split_select_view, parent, false);
 
-        floatingView.init(launcher, originalView, thumbnail, icon, positionOut);
+        floatingView.init(launcher, originalView, thumbnail, icon, positionOut,
+                additionalOffsetter);
         parent.addView(floatingView);
         return floatingView;
     }
@@ -120,7 +131,12 @@
         Utilities.getBoundsForViewInDragLayer(mActivity.getDragLayer(), originalView, viewBounds,
                 true /* ignoreTransform */, null /* recycle */,
                 mStartingPosition);
-        mStartingPosition.offset(originalView.getTranslationX(), originalView.getTranslationY());
+        if (mAdditionalOffsetter != null) {
+            mAdditionalOffsetter.accept(mStartingPosition);
+        } else {
+            mStartingPosition.offset(originalView.getTranslationX(),
+                    originalView.getTranslationY());
+        }
         final InsettableFrameLayout.LayoutParams lp = new InsettableFrameLayout.LayoutParams(
                 Math.round(mStartingPosition.width()),
                 Math.round(mStartingPosition.height()));
@@ -176,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);
@@ -205,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.
@@ -225,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 2a2d1dd..a042bff 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -992,13 +992,17 @@
      */
     public void launchSideTaskInLiveTileModeForRestartedApp(int taskId) {
         int runningTaskViewId = getTaskViewIdFromTaskId(taskId);
-        if (mRunningTaskViewId != -1 && mRunningTaskViewId == runningTaskViewId) {
-            TransformParams params = mRemoteTargetHandles[0].getTransformParams();
-            RemoteAnimationTargets targets = params.getTargetSet();
-            if (targets != null && targets.findTask(taskId) != null) {
-                launchSideTaskInLiveTileMode(taskId, targets.apps, targets.wallpapers,
-                        targets.nonApps);
-            }
+        if (mRunningTaskViewId == -1 ||
+                mRunningTaskViewId != runningTaskViewId ||
+                mRemoteTargetHandles == null) {
+            return;
+        }
+
+        TransformParams params = mRemoteTargetHandles[0].getTransformParams();
+        RemoteAnimationTargets targets = params.getTargetSet();
+        if (targets != null && targets.findTask(taskId) != null) {
+            launchSideTaskInLiveTileMode(taskId, targets.apps, targets.wallpapers,
+                    targets.nonApps);
         }
     }
 
@@ -2723,19 +2727,24 @@
         if (mSplitHiddenTaskView != null) {
             mSplitHiddenTaskView.setVisibility(INVISIBLE);
             mFirstFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
-                    mSplitHiddenTaskView, mSplitHiddenTaskView.getThumbnail().getThumbnail(),
-                    mSplitHiddenTaskView.getIconView().getDrawable(), startingTaskRect);
+                    mSplitHiddenTaskView.getThumbnail(),
+                    mSplitHiddenTaskView.getThumbnail().getThumbnail(),
+                    mSplitHiddenTaskView.getIconView().getDrawable(), startingTaskRect,
+                    floatingTaskViewStartingPosition -> floatingTaskViewStartingPosition.offset(
+                            mSplitHiddenTaskView.getTranslationX(),
+                            mSplitHiddenTaskView.getTranslationY()
+                    ));
             mFirstFloatingTaskView.setAlpha(1);
             mFirstFloatingTaskView.addAnimation(anim, startingTaskRect,
-                    mTempRect, mSplitHiddenTaskView, true /*fadeWithThumbnail*/);
+                    mTempRect, true /*fadeWithThumbnail*/);
         } else {
             mSplitSelectSource.view.setVisibility(INVISIBLE);
             mFirstFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
                     mSplitSelectSource.view, null,
-                    mSplitSelectSource.drawable, startingTaskRect);
+                    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) {
@@ -4019,16 +4028,19 @@
 
         mFirstFloatingTaskView.getBoundsOnScreen(firstTaskStartingBounds);
         mFirstFloatingTaskView.addAnimation(pendingAnimation,
-                new RectF(firstTaskStartingBounds), firstTaskEndingBounds, mFirstFloatingTaskView,
+                new RectF(firstTaskStartingBounds), firstTaskEndingBounds,
                 false /*fadeWithThumbnail*/);
 
         mSecondFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
-                taskView, taskView.getThumbnail().getThumbnail(),
-                taskView.getIconView().getDrawable(), secondTaskStartingBounds);
+                taskView.getThumbnail(), taskView.getThumbnail().getThumbnail(),
+                taskView.getIconView().getDrawable(), secondTaskStartingBounds,
+                floatingTaskViewStartingPosition -> floatingTaskViewStartingPosition.offset(
+                        taskView.getTranslationX(),
+                        taskView.getTranslationY()
+                ));
         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/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index ec96c6d..42f7053 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -34,6 +34,7 @@
 
 import androidx.annotation.IntDef;
 
+import com.android.launcher3.DeviceProfile.DeviceProfileListenable;
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.util.SystemUiController;
@@ -44,11 +45,13 @@
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Launcher BaseActivity
  */
-public abstract class BaseActivity extends Activity implements ActivityContext {
+public abstract class BaseActivity extends Activity implements ActivityContext,
+        DeviceProfileListenable {
 
     private static final String TAG = "BaseActivity";
 
@@ -142,6 +145,11 @@
         return mDeviceProfile;
     }
 
+    @Override
+    public List<OnDeviceProfileChangeListener> getOnDeviceProfileChangeListeners() {
+        return mDPChangeListeners;
+    }
+
     /**
      * Returns {@link StatsLogManager} for user event logging.
      */
@@ -261,20 +269,6 @@
 
     protected void onActivityFlagsChanged(int changeBits) { }
 
-    public void addOnDeviceProfileChangeListener(OnDeviceProfileChangeListener listener) {
-        mDPChangeListeners.add(listener);
-    }
-
-    public void removeOnDeviceProfileChangeListener(OnDeviceProfileChangeListener listener) {
-        mDPChangeListeners.remove(listener);
-    }
-
-    protected void dispatchDeviceProfileChanged() {
-        for (int i = mDPChangeListeners.size() - 1; i >= 0; i--) {
-            mDPChangeListeners.get(i).onDeviceProfileChanged(mDeviceProfile);
-        }
-    }
-
     public void addMultiWindowModeChangedListener(MultiWindowModeChangedListener listener) {
         mMultiWindowModeChangedListeners.add(listener);
     }
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index c456a4d..62703ad 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -44,6 +44,7 @@
 import com.android.launcher3.util.WindowBounds;
 
 import java.io.PrintWriter;
+import java.util.List;
 
 @SuppressLint("NewApi")
 public class DeviceProfile {
@@ -1223,6 +1224,35 @@
         void onDeviceProfileChanged(DeviceProfile dp);
     }
 
+    /** Allows registering listeners for {@link DeviceProfile} changes. */
+    public interface DeviceProfileListenable {
+
+        /** The current device profile. */
+        DeviceProfile getDeviceProfile();
+
+        /** Registered {@link OnDeviceProfileChangeListener} instances. */
+        List<OnDeviceProfileChangeListener> getOnDeviceProfileChangeListeners();
+
+        /** Notifies listeners of a {@link DeviceProfile} change. */
+        default void dispatchDeviceProfileChanged() {
+            DeviceProfile deviceProfile = getDeviceProfile();
+            List<OnDeviceProfileChangeListener> listeners = getOnDeviceProfileChangeListeners();
+            for (int i = listeners.size() - 1; i >= 0; i--) {
+                listeners.get(i).onDeviceProfileChanged(deviceProfile);
+            }
+        }
+
+        /** Register listener for {@link DeviceProfile} changes. */
+        default void addOnDeviceProfileChangeListener(OnDeviceProfileChangeListener listener) {
+            getOnDeviceProfileChangeListeners().add(listener);
+        }
+
+        /** Unregister listener for {@link DeviceProfile} changes. */
+        default void removeOnDeviceProfileChangeListener(OnDeviceProfileChangeListener listener) {
+            getOnDeviceProfileChangeListeners().remove(listener);
+        }
+    }
+
     public static class Builder {
         private Context mContext;
         private InvariantDeviceProfile mInv;
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index a381787..dfe4bb0 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -250,11 +250,12 @@
      * Reinitialize the current grid after a restore, where some grids might now be disabled.
      */
     public void reinitializeAfterRestore(Context context) {
+        String currentGridName = getCurrentGridName(context);
         String currentDbFile = dbFile;
-        String gridName = getCurrentGridName(context);
-        String newGridName = initGrid(context, gridName);
-        if (!newGridName.equals(gridName)) {
-            Log.d(TAG, "Restored grid is disabled : " + gridName
+        String newGridName = initGrid(context, currentGridName);
+        String newDbFile = dbFile;
+        if (!newDbFile.equals(currentDbFile)) {
+            Log.d(TAG, "Restored grid is disabled : " + currentGridName
                     + ", migrating to: " + newGridName
                     + ", removing all other grid db files");
             for (String gridDbFile : LauncherFiles.GRID_DB_FILES) {
@@ -265,7 +266,7 @@
                     Log.d(TAG, "Removed old grid db file: " + gridDbFile);
                 }
             }
-            setCurrentGrid(context, gridName);
+            setCurrentGrid(context, newGridName);
         }
     }
 
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index b75b610..8fb2f53 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -351,7 +351,7 @@
     // We only want to get the SharedPreferences once since it does an FS stat each time we get
     // it from the context.
     private SharedPreferences mSharedPrefs;
-    private OnboardingPrefs mOnboardingPrefs;
+    private OnboardingPrefs<? extends Launcher> mOnboardingPrefs;
 
     // Activity result which needs to be processed after workspace has loaded.
     private ActivityResultInfo mPendingActivityResult;
@@ -557,11 +557,12 @@
         return new LauncherOverlayManager() { };
     }
 
-    protected OnboardingPrefs createOnboardingPrefs(SharedPreferences sharedPrefs) {
+    protected OnboardingPrefs<? extends Launcher> createOnboardingPrefs(
+            SharedPreferences sharedPrefs) {
         return new OnboardingPrefs<>(this, sharedPrefs);
     }
 
-    public OnboardingPrefs getOnboardingPrefs() {
+    public OnboardingPrefs<? extends Launcher> getOnboardingPrefs() {
         return mOnboardingPrefs;
     }
 
@@ -588,7 +589,7 @@
     }
 
     @Override
-    protected void dispatchDeviceProfileChanged() {
+    public void dispatchDeviceProfileChanged() {
         super.dispatchDeviceProfileChanged();
         mOverlayManager.onDeviceProvideChanged();
     }
@@ -1780,6 +1781,11 @@
         return mWorkspaceLoading;
     }
 
+    @Override
+    public boolean isBindingItems() {
+        return mWorkspaceLoading;
+    }
+
     private void setWorkspaceLoading(boolean value) {
         mWorkspaceLoading = value;
     }
diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java
index b56c012..4300392 100644
--- a/src/com/android/launcher3/LauncherAnimUtils.java
+++ b/src/com/android/launcher3/LauncherAnimUtils.java
@@ -27,6 +27,8 @@
 import android.view.View;
 import android.view.ViewGroup.LayoutParams;
 
+import com.android.launcher3.util.MultiScalePropertyFactory;
+
 public class LauncherAnimUtils {
     /**
      * Durations for various state animations. These are not defined in resources to allow
@@ -64,6 +66,25 @@
                 }
             };
 
+    /**
+     * Property to set the scale of workspace and hotseat. The value is based on a combination
+     * of all the ones set, to have a smooth experience even in the case of overlapping scaling
+     * animation.
+     */
+    public static final MultiScalePropertyFactory<View> SCALE_PROPERTY_FACTORY =
+            new MultiScalePropertyFactory<View>("scale_property") {
+                @Override
+                protected void apply(View view, float scale) {
+                    view.setScaleX(scale);
+                    view.setScaleY(scale);
+                }
+            };
+
+    public static final int SCALE_INDEX_UNFOLD_ANIMATION = 1;
+    public static final int SCALE_INDEX_UNLOCK_ANIMATION = 2;
+    public static final int SCALE_INDEX_WORKSPACE_STATE = 3;
+    public static final int SCALE_INDEX_REVEAL_ANIM = 4;
+
     /** Increase the duration if we prevented the fling, as we are going against a high velocity. */
     public static int blockedFlingDurationFactor(float velocity) {
         return (int) Utilities.boundToRange(Math.abs(velocity) / 2, 2f, 6f);
diff --git a/src/com/android/launcher3/LauncherBackupAgent.java b/src/com/android/launcher3/LauncherBackupAgent.java
index dc533f0..3d2700d 100644
--- a/src/com/android/launcher3/LauncherBackupAgent.java
+++ b/src/com/android/launcher3/LauncherBackupAgent.java
@@ -8,8 +8,13 @@
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.provider.RestoreDbTask;
 
+import java.io.File;
+import java.io.IOException;
+
 public class LauncherBackupAgent extends BackupAgent {
 
+    private static final String TAG = "LauncherBackupAgent";
+
     @Override
     public void onCreate() {
         super.onCreate();
@@ -24,6 +29,17 @@
     }
 
     @Override
+    public void onRestoreFile(ParcelFileDescriptor data, long size, File destination, int type,
+            long mode, long mtime) throws IOException {
+        // Remove old files which might contain obsolete attributes like idp_grid_name in shared
+        // preference that will obstruct backup's attribute from writing to shared preferences.
+        if (destination.delete()) {
+            FileLog.d("LauncherBackupAgent", "Removed obsolete file: " + destination);
+        }
+        super.onRestoreFile(data, size, destination, type, mode, mtime);
+    }
+
+    @Override
     public void onBackup(
             ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState) {
         // Doesn't do incremental backup/restore
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/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 1b9647a..98e785f 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -18,7 +18,8 @@
 
 import static androidx.dynamicanimation.animation.DynamicAnimation.MIN_VISIBLE_CHANGE_SCALE;
 
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.LauncherAnimUtils.SCALE_INDEX_WORKSPACE_STATE;
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY_FACTORY;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
@@ -42,6 +43,7 @@
 import static com.android.launcher3.states.StateAnimationConfig.SKIP_SCRIM;
 
 import android.animation.ValueAnimator;
+import android.util.FloatProperty;
 import android.view.View;
 import android.view.animation.Interpolator;
 
@@ -62,6 +64,9 @@
  */
 public class WorkspaceStateTransitionAnimation {
 
+    private static final FloatProperty<View> WORKSPACE_STATE_SCALE_PROPERTY =
+            SCALE_PROPERTY_FACTORY.get(SCALE_INDEX_WORKSPACE_STATE);
+
     private final Launcher mLauncher;
     private final Workspace mWorkspace;
 
@@ -117,7 +122,8 @@
             ((PendingAnimation) propertySetter).add(getSpringScaleAnimator(mLauncher,
                     mWorkspace, mNewScale));
         } else {
-            propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, scaleInterpolator);
+            propertySetter.setFloat(mWorkspace, WORKSPACE_STATE_SCALE_PROPERTY, mNewScale,
+                    scaleInterpolator);
         }
 
         mWorkspace.setPivotToScaleWithSelf(hotseat);
@@ -128,7 +134,7 @@
         } else {
             Interpolator hotseatScaleInterpolator = config.getInterpolator(ANIM_HOTSEAT_SCALE,
                     scaleInterpolator);
-            propertySetter.setFloat(hotseat, SCALE_PROPERTY, hotseatScale,
+            propertySetter.setFloat(hotseat, WORKSPACE_STATE_SCALE_PROPERTY, hotseatScale,
                     hotseatScaleInterpolator);
         }
 
@@ -205,9 +211,9 @@
                 .setDampingRatio(damping)
                 .setMinimumVisibleChange(MIN_VISIBLE_CHANGE_SCALE)
                 .setEndValue(scale)
-                .setStartValue(SCALE_PROPERTY.get(v))
+                .setStartValue(WORKSPACE_STATE_SCALE_PROPERTY.get(v))
                 .setStartVelocity(velocityPxPerS)
-                .build(v, SCALE_PROPERTY);
+                .build(v, WORKSPACE_STATE_SCALE_PROPERTY);
 
     }
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index fb87f88..114f813 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -62,7 +62,6 @@
 
     public ActivityAllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
-        mActivityContext.addOnDeviceProfileChangeListener(this);
     }
 
     public SearchUiManager getSearchUiManager() {
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
index a66ae78..59e21c0 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
@@ -48,6 +48,7 @@
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.DeviceProfile.DeviceProfileListenable;
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget.DragObject;
@@ -75,9 +76,10 @@
  *
  * @param <T> Type of context inflating all apps.
  */
-public abstract class BaseAllAppsContainerView<T extends Context & ActivityContext> extends
-        SpringRelativeLayout implements DragSource, Insettable, OnDeviceProfileChangeListener,
-        OnActivePageChangedListener, ScrimView.ScrimDrawingController {
+public abstract class BaseAllAppsContainerView<T extends Context & ActivityContext
+        & DeviceProfileListenable> extends SpringRelativeLayout implements DragSource, Insettable,
+        OnDeviceProfileChangeListener, OnActivePageChangedListener,
+        ScrimView.ScrimDrawingController {
 
     private static final String BUNDLE_KEY_CURRENT_PAGE = "launcher.allapps.current_page";
 
@@ -148,6 +150,7 @@
         mNavBarScrimPaint.setColor(Themes.getAttrColor(context, R.attr.allAppsNavBarScrimColor));
 
         mAllAppsStore.addUpdateListener(this::onAppsUpdated);
+        mActivityContext.addOnDeviceProfileChangeListener(this);
     }
 
     /** Creates the adapter provider for the main section. */
@@ -580,7 +583,10 @@
 
     void setupHeader() {
         mHeader.setVisibility(View.VISIBLE);
-        mHeader.setup(mAH, mAH.get(AdapterHolder.WORK).mRecyclerView == null);
+        mHeader.setup(
+                mAH.get(AdapterHolder.MAIN).mRecyclerView,
+                mAH.get(AdapterHolder.WORK).mRecyclerView,
+                mAH.get(AdapterHolder.WORK).mRecyclerView == null);
 
         int padding = mHeader.getMaxTranslation();
         for (int i = 0; i < mAH.size(); i++) {
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index 059d4b8..ed8761e 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -41,7 +41,6 @@
 import com.android.systemui.plugins.PluginListener;
 
 import java.util.ArrayList;
-import java.util.List;
 import java.util.Map;
 
 public class FloatingHeaderView extends LinearLayout implements
@@ -226,8 +225,7 @@
         return super.getFocusedChild();
     }
 
-    <T extends Context & ActivityContext> void setup(
-            List<BaseAllAppsContainerView<T>.AdapterHolder> mAH, boolean tabsHidden) {
+    void setup(AllAppsRecyclerView mainRV, AllAppsRecyclerView workRV, boolean tabsHidden) {
         for (FloatingHeaderRow row : mAllRows) {
             row.setup(this, mAllRows, tabsHidden);
         }
@@ -235,10 +233,8 @@
 
         mTabsHidden = tabsHidden;
         mTabLayout.setVisibility(tabsHidden ? View.GONE : View.VISIBLE);
-        mMainRV = setupRV(mMainRV,
-                mAH.get(BaseAllAppsContainerView.AdapterHolder.MAIN).mRecyclerView);
-        mWorkRV = setupRV(mWorkRV,
-                mAH.get(BaseAllAppsContainerView.AdapterHolder.WORK).mRecyclerView);
+        mMainRV = setupRV(mMainRV, mainRV);
+        mWorkRV = setupRV(mWorkRV, workRV);
         mParent = (ViewGroup) mMainRV.getParent();
         setMainActive(mMainRVActive || mWorkRV == null);
         reset(false);
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 99c9bb9..a949e11 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -139,12 +139,12 @@
 
     public static final BooleanFlag ENABLE_BULK_WORKSPACE_ICON_LOADING = getDebugFlag(
             "ENABLE_BULK_WORKSPACE_ICON_LOADING",
-            false,
+            true,
             "Enable loading workspace icons in bulk.");
 
     public static final BooleanFlag ENABLE_BULK_ALL_APPS_ICON_LOADING = getDebugFlag(
             "ENABLE_BULK_ALL_APPS_ICON_LOADING",
-            false,
+            true,
             "Enable loading all apps icons in bulk.");
 
     // Keep as DeviceFlag for remote disable in emergency.
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 8313571..b914ae2 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/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 88ce57e..2459e09 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -111,7 +111,7 @@
  *   3) Place appropriate elements like icons and first-page qsb
  *   4) Measure and draw the view on a canvas
  */
-@TargetApi(Build.VERSION_CODES.O)
+@TargetApi(Build.VERSION_CODES.R)
 public class LauncherPreviewRenderer extends ContextWrapper
         implements ActivityContext, WorkspaceLayoutManager, LayoutInflater.Factory2 {
 
@@ -186,19 +186,13 @@
         mIdp = idp;
         mDp = idp.getDeviceProfile(context).copy(context);
 
-        if (Utilities.ATLEAST_R) {
-            WindowInsets currentWindowInsets = context.getSystemService(WindowManager.class)
-                    .getCurrentWindowMetrics().getWindowInsets();
-            mInsets = new Rect(
-                    currentWindowInsets.getSystemWindowInsetLeft(),
-                    currentWindowInsets.getSystemWindowInsetTop(),
-                    currentWindowInsets.getSystemWindowInsetRight(),
-                    currentWindowInsets.getSystemWindowInsetBottom());
-        } else {
-            mInsets = new Rect();
-            mInsets.left = mInsets.right = (mDp.widthPx - mDp.availableWidthPx) / 2;
-            mInsets.top = mInsets.bottom = (mDp.heightPx - mDp.availableHeightPx) / 2;
-        }
+        WindowInsets currentWindowInsets = context.getSystemService(WindowManager.class)
+                .getCurrentWindowMetrics().getWindowInsets();
+        mInsets = new Rect(
+                currentWindowInsets.getSystemWindowInsetLeft(),
+                currentWindowInsets.getSystemWindowInsetTop(),
+                currentWindowInsets.getSystemWindowInsetRight(),
+                mDp.isTaskbarPresent ? 0 : currentWindowInsets.getSystemWindowInsetBottom());
         mDp.updateInsets(mInsets);
 
         BaseIconFactory iconFactory =
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/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 198397a..05d6fc6 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -508,6 +508,7 @@
     @Override
     protected void closeComplete() {
         super.closeComplete();
+        mActivityContext.getDragController().removeDragListener(this);
         PopupContainerWithArrow openPopup = getOpen(mActivityContext);
         if (openPopup == null || openPopup.mOriginalIcon != mOriginalIcon) {
             mOriginalIcon.setTextVisibility(mOriginalIcon.shouldTextBeVisible());
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index d994dbe..48b3acf 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -79,11 +79,15 @@
             helper.createEmptyDB(helper.getWritableDatabase());
         }
 
+        // Obtain InvariantDeviceProfile first before setting pending to false, so
+        // InvariantDeviceProfile won't switch to new grid when initializing.
+        InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(context);
+
         // Set is pending to false irrespective of the result, so that it doesn't get
         // executed again.
         Utilities.getPrefs(context).edit().remove(RESTORED_DEVICE_TYPE).commit();
 
-        InvariantDeviceProfile.INSTANCE.get(context).reinitializeAfterRestore(context);
+        idp.reinitializeAfterRestore(context);
     }
 
     private static boolean performRestore(Context context, DatabaseHelper helper) {
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index 8ebfd62..2eae99a 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -23,16 +23,23 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Insets;
+import android.graphics.Point;
+import android.graphics.Rect;
 import android.os.Build;
 import android.os.Bundle;
 import android.view.WindowInsets;
 
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.util.ResourceBasedOverride;
 import com.android.launcher3.widget.picker.WidgetsFullSheet;
 
@@ -62,12 +69,18 @@
         mLauncherAppState = LauncherAppState.getInstanceNoCreate();
     }
 
-    public Bundle call(String method) {
-        return call(method, /*arg=*/ null);
-    }
-
-    public Bundle call(String method, String arg) {
+    /**
+     * handle a request and return result Bundle.
+     *
+     * @param method request name.
+     * @param arg    optional single string argument.
+     * @param extra  extra request payload.
+     */
+    public Bundle call(String method, String arg, @Nullable Bundle extra) {
         final Bundle response = new Bundle();
+        if (extra != null && extra.getClassLoader() == null) {
+            extra.setClassLoader(getClass().getClassLoader());
+        }
         switch (method) {
             case TestProtocol.REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT: {
                 return getLauncherUIProperty(Bundle::putInt, l -> {
@@ -163,11 +176,48 @@
                                 .forceAllowRotationForTesting(Boolean.parseBoolean(arg)));
                 return null;
 
+            case TestProtocol.REQUEST_WORKSPACE_CELL_LAYOUT_SIZE:
+                return getLauncherUIProperty(Bundle::putIntArray, launcher -> {
+                    final Workspace workspace = launcher.getWorkspace();
+                    final int screenId = workspace.getScreenIdForPageIndex(
+                            workspace.getCurrentPage());
+                    final CellLayout cellLayout = workspace.getScreenWithId(screenId);
+                    return new int[]{cellLayout.getCountX(), cellLayout.getCountY()};
+                });
+
+            case TestProtocol.REQUEST_WORKSPACE_CELL_CENTER:
+                final WorkspaceCellCenterRequest request = extra.getParcelable(
+                        TestProtocol.TEST_INFO_REQUEST_FIELD);
+                return getLauncherUIProperty(Bundle::putParcelable, launcher -> {
+                    final Workspace workspace = launcher.getWorkspace();
+                    // TODO(b/216387249): allow caller selecting different pages.
+                    CellLayout cellLayout = (CellLayout) workspace.getPageAt(
+                            workspace.getCurrentPage());
+                    final Rect cellRect = getDescendantRectRelativeToDragLayerForCell(launcher,
+                            cellLayout, request.cellX, request.cellY, request.spanX, request.spanY);
+                    return new Point(cellRect.centerX(), cellRect.centerY());
+                });
+
             default:
                 return null;
         }
     }
 
+    private static Rect getDescendantRectRelativeToDragLayerForCell(Launcher launcher,
+            CellLayout cellLayout, int cellX, int cellY, int spanX, int spanY) {
+        final DragLayer dragLayer = launcher.getDragLayer();
+        final Rect target = new Rect();
+
+        cellLayout.cellToRect(cellX, cellY, spanX, spanY, target);
+        int[] leftTop = {target.left, target.top};
+        int[] rightBottom = {target.right, target.bottom};
+        dragLayer.getDescendantCoordRelativeToSelf(cellLayout, leftTop);
+        dragLayer.getDescendantCoordRelativeToSelf(cellLayout, rightBottom);
+
+        target.set(leftTop[0], leftTop[1], rightBottom[0], rightBottom[1]);
+        return target;
+    }
+
     protected boolean isLauncherInitialized() {
         return Launcher.ACTIVITY_TRACKER.getCreatedActivity() == null
                 || LauncherAppState.getInstance(mContext).getModel().isModelLoaded();
diff --git a/src/com/android/launcher3/testing/TestInformationProvider.java b/src/com/android/launcher3/testing/TestInformationProvider.java
index 4f2619c..bcc7c2d 100644
--- a/src/com/android/launcher3/testing/TestInformationProvider.java
+++ b/src/com/android/launcher3/testing/TestInformationProvider.java
@@ -60,7 +60,7 @@
         if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
             TestInformationHandler handler = TestInformationHandler.newInstance(getContext());
             handler.init(getContext());
-            return handler.call(method, arg);
+            return handler.call(method, arg, extras);
         }
         return null;
     }
diff --git a/src/com/android/launcher3/testing/TestInformationRequest.java b/src/com/android/launcher3/testing/TestInformationRequest.java
new file mode 100644
index 0000000..272ae56
--- /dev/null
+++ b/src/com/android/launcher3/testing/TestInformationRequest.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2021 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.testing;
+
+import android.os.Parcelable;
+
+/**
+ * A Request sent to TestInformationHandler can implement this interface to carry more information.
+ */
+public interface TestInformationRequest extends Parcelable {
+    /**
+     * The name for handler to dispatch request.
+     */
+    String getRequestName();
+}
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 78e9fe8..2bf3b14 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -68,19 +68,17 @@
         }
     }
 
+    public static final String TEST_INFO_REQUEST_FIELD = "request";
     public static final String TEST_INFO_RESPONSE_FIELD = "response";
 
     public static final String REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT =
             "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";
@@ -104,6 +102,10 @@
     public static final String REQUEST_GET_ACTIVITIES_CREATED_COUNT =
             "get-activities-created-count";
     public static final String REQUEST_GET_ACTIVITIES = "get-activities";
+
+    public static final String REQUEST_WORKSPACE_CELL_LAYOUT_SIZE = "workspace-cell-layout-size";
+    public static final String REQUEST_WORKSPACE_CELL_CENTER = "workspace-cell-center";
+
     public static final String REQUEST_GET_FOCUSED_TASK_HEIGHT_FOR_TABLET =
             "get-focused-task-height-for-tablet";
     public static final String REQUEST_GET_GRID_TASK_SIZE_RECT_FOR_TABLET =
diff --git a/src/com/android/launcher3/testing/WorkspaceCellCenterRequest.java b/src/com/android/launcher3/testing/WorkspaceCellCenterRequest.java
new file mode 100644
index 0000000..71ab09f
--- /dev/null
+++ b/src/com/android/launcher3/testing/WorkspaceCellCenterRequest.java
@@ -0,0 +1,138 @@
+/*
+ * 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.testing;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Request object for querying a workspace cell region in Rect.
+ */
+public class WorkspaceCellCenterRequest implements TestInformationRequest {
+    public final int cellX;
+    public final int cellY;
+    public final int spanX;
+    public final int spanY;
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(cellX);
+        dest.writeInt(cellY);
+        dest.writeInt(spanX);
+        dest.writeInt(spanY);
+    }
+
+    public static final Parcelable.Creator<WorkspaceCellCenterRequest> CREATOR =
+            new Parcelable.Creator<WorkspaceCellCenterRequest>() {
+
+                @Override
+                public WorkspaceCellCenterRequest createFromParcel(Parcel source) {
+                    return new WorkspaceCellCenterRequest(source);
+                }
+
+                @Override
+                public WorkspaceCellCenterRequest[] newArray(int size) {
+                    return new WorkspaceCellCenterRequest[size];
+                }
+            };
+
+    private WorkspaceCellCenterRequest(int cellX, int cellY, int spanX, int spanY) {
+        this.cellX = cellX;
+        this.cellY = cellY;
+        this.spanX = spanX;
+        this.spanY = spanY;
+    }
+
+    private WorkspaceCellCenterRequest(Parcel in) {
+        this(in.readInt(), in.readInt(), in.readInt(), in.readInt());
+    }
+
+    /**
+     * Create a builder for WorkspaceCellRectRequest.
+     *
+     * @return WorkspaceCellRectRequest builder.
+     */
+    public static WorkspaceCellCenterRequest.Builder builder() {
+        return new WorkspaceCellCenterRequest.Builder();
+    }
+
+    @Override
+    public String getRequestName() {
+        return TestProtocol.REQUEST_WORKSPACE_CELL_CENTER;
+    }
+
+    /**
+     * WorkspaceCellRectRequest Builder.
+     */
+    public static final class Builder {
+        private int mCellX;
+        private int mCellY;
+        private int mSpanX;
+        private int mSpanY;
+
+        private Builder() {
+            this.mCellX = 0;
+            this.mCellY = 0;
+            this.mSpanX = 1;
+            this.mSpanY = 1;
+        }
+
+        /**
+         * Set X coordinate of upper left corner expressed as a cell position
+         */
+        public WorkspaceCellCenterRequest.Builder setCellX(int x) {
+            this.mCellX = x;
+            return this;
+        }
+
+        /**
+         * Set Y coordinate of upper left corner expressed as a cell position
+         */
+        public WorkspaceCellCenterRequest.Builder setCellY(int y) {
+            this.mCellY = y;
+            return this;
+        }
+
+        /**
+         * Set span Width in cells
+         */
+        public WorkspaceCellCenterRequest.Builder setSpanX(int x) {
+            this.mSpanX = x;
+            return this;
+        }
+
+        /**
+         * Set span Height in cells
+         */
+        public WorkspaceCellCenterRequest.Builder setSpanY(int y) {
+            this.mCellY = y;
+            return this;
+        }
+
+        /**
+         * build the WorkspaceCellRectRequest.
+         */
+        public WorkspaceCellCenterRequest build() {
+            return new WorkspaceCellCenterRequest(mCellX, mCellY, mSpanX, mSpanY);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index a308182..0d92e25 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -421,11 +421,11 @@
         // Add both left and right options if we're in tablet mode
         if (dp.isTablet && dp.isLandscape) {
             options.add(new SplitPositionOption(
-                    R.drawable.ic_split_right, R.string.split_screen_position_right,
-                    STAGE_POSITION_BOTTOM_OR_RIGHT, STAGE_TYPE_MAIN));
-            options.add(new SplitPositionOption(
                     R.drawable.ic_split_left, R.string.split_screen_position_left,
                     STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
+            options.add(new SplitPositionOption(
+                    R.drawable.ic_split_right, R.string.split_screen_position_right,
+                    STAGE_POSITION_BOTTOM_OR_RIGHT, STAGE_TYPE_MAIN));
         } else {
             if (dp.isSeascape()) {
                 // Add left/right options
diff --git a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
index 6dc0c9a..69e19f4 100644
--- a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
@@ -164,7 +164,7 @@
         // Add "right" option which is actually the top
         return Collections.singletonList(new SplitPositionOption(
                 R.drawable.ic_split_right, R.string.split_screen_position_right,
-                STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
+                STAGE_POSITION_BOTTOM_OR_RIGHT, STAGE_TYPE_MAIN));
     }
 
     @Override
diff --git a/src/com/android/launcher3/util/MultiScalePropertyFactory.java b/src/com/android/launcher3/util/MultiScalePropertyFactory.java
new file mode 100644
index 0000000..f27d0f0
--- /dev/null
+++ b/src/com/android/launcher3/util/MultiScalePropertyFactory.java
@@ -0,0 +1,109 @@
+/*
+ * 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.util;
+
+import android.util.ArrayMap;
+import android.util.FloatProperty;
+
+import com.android.launcher3.Utilities;
+
+/**
+ * Allows to combine multiple values set by several sources.
+ *
+ * The various sources are meant to use [set], providing different `setterIndex` params. When it is
+ * not set, 0 is used. This is meant to cover the case multiple animations are going on at the same
+ * time.
+ *
+ * This class behaves similarly to [MultiValueAlpha], but is meant to be more abstract and reusable.
+ * It sets the multiplication of all values, bounded to the max and the min values.
+ *
+ * @param <T> Type where to apply the property.
+ */
+public abstract class MultiScalePropertyFactory<T> {
+
+    private final String mName;
+    private final ArrayMap<Integer, MultiScaleProperty> mProperties =
+            new ArrayMap<Integer, MultiScaleProperty>();
+
+    // This is an optimization for cases when set is called repeatedly with the same setterIndex.
+    private float mMinOfOthers = 0;
+    private float mMaxOfOthers = 0;
+    private float mMultiplicationOfOthers = 0;
+    private Integer mLastIndexSet = -1;
+    private float mLastAggregatedValue = 1.0f;
+
+    public MultiScalePropertyFactory(String name) {
+        mName = name;
+    }
+
+    /** Returns the [MultiFloatProperty] associated with [inx], creating it if not present. */
+    public MultiScaleProperty get(Integer index) {
+        return mProperties.computeIfAbsent(index,
+                (k) -> new MultiScaleProperty(index, mName + "_" + index));
+    }
+
+
+    /**
+     * Each [setValue] will be aggregated with the other properties values created by the
+     * corresponding factory.
+     */
+    class MultiScaleProperty extends FloatProperty<T> {
+        private final int mInx;
+        private float mValue = 1.0f;
+
+        MultiScaleProperty(int inx, String name) {
+            super(name);
+            mInx = inx;
+        }
+
+        @Override
+        public void setValue(T obj, float newValue) {
+            if (mLastIndexSet != mInx) {
+                mMinOfOthers = Float.MAX_VALUE;
+                mMaxOfOthers = Float.MIN_VALUE;
+                mMultiplicationOfOthers = 1.0f;
+                mProperties.forEach((key, property) -> {
+                    if (key != mInx) {
+                        mMinOfOthers = Math.min(mMinOfOthers, property.mValue);
+                        mMaxOfOthers = Math.max(mMaxOfOthers, property.mValue);
+                        mMultiplicationOfOthers *= property.mValue;
+                    }
+                });
+                mLastIndexSet = mInx;
+            }
+            float minValue = Math.min(mMinOfOthers, newValue);
+            float maxValue = Math.max(mMaxOfOthers, newValue);
+            float multValue = mMultiplicationOfOthers * newValue;
+            mLastAggregatedValue = Utilities.boundToRange(multValue, minValue, maxValue);
+            mValue = newValue;
+            apply(obj, mLastAggregatedValue);
+        }
+
+        @Override
+        public Float get(T t) {
+            return mLastAggregatedValue;
+        }
+
+        @Override
+        public String toString() {
+            return String.valueOf(mValue);
+        }
+    }
+
+    /** Applies value to object after setValue method is called. */
+    protected abstract void apply(T obj, float value);
+}
diff --git a/src/com/android/launcher3/util/OnboardingPrefs.java b/src/com/android/launcher3/util/OnboardingPrefs.java
index 5ba0d30..39d7cfe 100644
--- a/src/com/android/launcher3/util/OnboardingPrefs.java
+++ b/src/com/android/launcher3/util/OnboardingPrefs.java
@@ -20,7 +20,7 @@
 
 import androidx.annotation.StringDef;
 
-import com.android.launcher3.Launcher;
+import com.android.launcher3.views.ActivityContext;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -29,8 +29,10 @@
 
 /**
  * Stores and retrieves onboarding-related data via SharedPreferences.
+ *
+ * @param <T> Context which owns these preferences.
  */
-public class OnboardingPrefs<T extends Launcher> {
+public class OnboardingPrefs<T extends ActivityContext> {
 
     public static final String HOME_BOUNCE_SEEN = "launcher.apps_view_shown";
     public static final String HOME_BOUNCE_COUNT = "launcher.home_bounce_count";
@@ -39,13 +41,15 @@
     public static final String SEARCH_EDU_SEEN = "launcher.search_edu_seen";
     public static final String SEARCH_SNACKBAR_COUNT = "launcher.keyboard_snackbar_count";
     public static final String TASKBAR_EDU_SEEN = "launcher.taskbar_edu_seen";
+    public static final String ALL_APPS_VISITED_COUNT = "launcher.all_apps_visited_count";
     // When adding a new key, add it here as well, to be able to reset it from Developer Options.
     public static final Map<String, String[]> ALL_PREF_KEYS = Map.of(
             "All Apps Bounce", new String[] { HOME_BOUNCE_SEEN, HOME_BOUNCE_COUNT },
             "Hybrid Hotseat Education", new String[] { HOTSEAT_DISCOVERY_TIP_COUNT,
                     HOTSEAT_LONGPRESS_TIP_SEEN },
             "Search Education", new String[] { SEARCH_EDU_SEEN, SEARCH_SNACKBAR_COUNT },
-            "Taskbar Education", new String[] { TASKBAR_EDU_SEEN }
+            "Taskbar Education", new String[] { TASKBAR_EDU_SEEN },
+            "All Apps Visited Count", new String[] {ALL_APPS_VISITED_COUNT}
     );
 
     /**
@@ -67,7 +71,8 @@
     @StringDef(value = {
             HOME_BOUNCE_COUNT,
             HOTSEAT_DISCOVERY_TIP_COUNT,
-            SEARCH_SNACKBAR_COUNT
+            SEARCH_SNACKBAR_COUNT,
+            ALL_APPS_VISITED_COUNT
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface EventCountKey {
@@ -80,6 +85,7 @@
         maxCounts.put(HOME_BOUNCE_COUNT, 3);
         maxCounts.put(HOTSEAT_DISCOVERY_TIP_COUNT, 5);
         maxCounts.put(SEARCH_SNACKBAR_COUNT, 3);
+        maxCounts.put(ALL_APPS_VISITED_COUNT, 20);
         MAX_COUNTS = Collections.unmodifiableMap(maxCounts);
     }
 
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index a318363..3c90eea 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -34,6 +34,7 @@
 import com.android.launcher3.model.StringCache;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.popup.PopupDataProvider;
+import com.android.launcher3.util.OnboardingPrefs;
 import com.android.launcher3.util.ViewCache;
 
 /**
@@ -138,6 +139,16 @@
      */
     default void applyOverwritesToLogItem(LauncherAtom.ItemInfo.Builder itemInfoBuilder) { }
 
+    /** Onboarding preferences for any onboarding data within this context. */
+    default OnboardingPrefs<?> getOnboardingPrefs() {
+        return null;
+    }
+
+    /** Returns {@code true} if items are currently being bound within this context. */
+    default boolean isBindingItems() {
+        return false;
+    }
+
     /**
      * Returns the ActivityContext associated with the given Context, or throws an exception if
      * the Context is not associated with any ActivityContext.
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index 55524dd..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++;
@@ -560,6 +564,12 @@
                 launcher, parent);
         view.recycle();
 
+        // Init properties before getting the drawable.
+        view.mIsVerticalBarLayout = launcher.getDeviceProfile().isVerticalBarLayout();
+        view.mIsOpening = isOpening;
+        view.mOriginalIcon = originalView;
+        view.mPositionOut = positionOut;
+
         // Get the drawable on the background thread
         boolean shouldLoadIcon = originalView.getTag() instanceof ItemInfo && hideOriginal;
         if (shouldLoadIcon) {
@@ -573,11 +583,6 @@
         }
         sIconLoadResult = null;
 
-        view.mIsVerticalBarLayout = launcher.getDeviceProfile().isVerticalBarLayout();
-        view.mIsOpening = isOpening;
-        view.mOriginalIcon = originalView;
-        view.mPositionOut = positionOut;
-
         // Match the position of the original view.
         view.matchPositionOf(launcher, originalView, isOpening, positionOut);
 
@@ -635,6 +640,7 @@
         mLoadIconSignal = null;
         mEndRunnable = null;
         mFinalDrawableBounds.setEmpty();
+        mIsOpening = false;
         mPositionOut = null;
         mListenerView.setListener(null);
         mOriginalIcon = null;
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index 95bce31..867e488 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -180,18 +180,6 @@
      */
     public static ArrayList<OptionItem> getOptions(Launcher launcher) {
         ArrayList<OptionItem> options = new ArrayList<>();
-        options.add(new OptionItem(launcher,
-                R.string.settings_button_text,
-                R.drawable.ic_setting,
-                LAUNCHER_SETTINGS_BUTTON_TAP_OR_LONGPRESS,
-                OptionsPopupView::startSettings));
-        if (!WidgetsModel.GO_DISABLE_WIDGETS) {
-            options.add(new OptionItem(launcher,
-                    R.string.widget_button_text,
-                    R.drawable.ic_widget,
-                    LAUNCHER_WIDGETSTRAY_BUTTON_TAP_OR_LONGPRESS,
-                    OptionsPopupView::onWidgetsClicked));
-        }
         int resString = Utilities.existsStyleWallpapers(launcher) ?
                 R.string.styles_wallpaper_button_text : R.string.wallpaper_button_text;
         int resDrawable = Utilities.existsStyleWallpapers(launcher) ?
@@ -201,6 +189,18 @@
                 resDrawable,
                 IGNORE,
                 OptionsPopupView::startWallpaperPicker));
+        if (!WidgetsModel.GO_DISABLE_WIDGETS) {
+            options.add(new OptionItem(launcher,
+                    R.string.widget_button_text,
+                    R.drawable.ic_widget,
+                    LAUNCHER_WIDGETSTRAY_BUTTON_TAP_OR_LONGPRESS,
+                    OptionsPopupView::onWidgetsClicked));
+        }
+        options.add(new OptionItem(launcher,
+                R.string.settings_button_text,
+                R.drawable.ic_setting,
+                LAUNCHER_SETTINGS_BUTTON_TAP_OR_LONGPRESS,
+                OptionsPopupView::startSettings));
         return options;
     }
 
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 4b4f1d9..da8bf6e 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -24,6 +24,8 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
+import android.graphics.Point;
+
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -36,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;
@@ -213,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);
     }
 
@@ -226,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();
         }
@@ -235,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));
 
@@ -284,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.
@@ -311,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);
@@ -323,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
@@ -332,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)) {
@@ -362,28 +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 {
-                appIcon = allApps.getAppIcon(name);
-                appIcon.dragToWorkspace(false, false);
+                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);
 
@@ -397,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();
@@ -414,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,15 +434,15 @@
     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);
+            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",
@@ -468,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();
@@ -481,7 +483,39 @@
         }
     }
 
+    @Test
+    @PortraitLandscape
+    public void testDragAppIconToWorkspaceCell() throws Exception {
+        final Point dimensions = mLauncher.getWorkspace().getIconGridDimensions();
+
+        Point[] targets = {
+                new Point(0, 1),
+                new Point(0, dimensions.y - 2),
+                new Point(dimensions.x - 1, 1),
+                new Point(dimensions.x - 1, dimensions.y - 2),
+                new Point(dimensions.x / 2, dimensions.y / 2)
+        };
+
+        for (Point target : targets) {
+            final HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
+            allApps.freeze();
+            try {
+                allApps.getAppIcon(APP_NAME).dragToWorkspace(target.x, target.y);
+            } finally {
+                allApps.unfreeze();
+            }
+            // Reset the workspace for the next shortcut creation.
+            initialize(this);
+        }
+
+        // test to move a shortcut to other cell.
+        final HomeAppIcon launcherTestAppIcon = createShortcutIfNotExist(APP_NAME);
+        for (Point target : targets) {
+            launcherTestAppIcon.dragToWorkspace(target.x, target.y);
+        }
+    }
+
     public static String getAppPackageName() {
         return getInstrumentation().getContext().getPackageName();
     }
-}
\ No newline at end of file
+}
diff --git a/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java b/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java
index 122a130..d0daefc 100644
--- a/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java
@@ -64,8 +64,7 @@
     }
 
     @Test
-    // TODO(b/197631877) Enable in portrait.
-    // @PortraitLandscape
+    @PortraitLandscape
     public void testDragIconToRightPanel() {
         Workspace workspace = mLauncher.getWorkspace();
 
@@ -79,8 +78,7 @@
     }
 
     @Test
-    // TODO(b/197631877) Enable in portrait.
-    // @PortraitLandscape
+    @PortraitLandscape
     public void testSinglePageDragIconWhenMultiplePageScrollingIsPossible() {
         Workspace workspace = mLauncher.getWorkspace();
 
@@ -134,8 +132,7 @@
     }
 
     @Test
-    // TODO(b/197631877) Enable in portrait.
-    // @PortraitLandscape
+    @PortraitLandscape
     public void testDragIconToPage2() {
         Workspace workspace = mLauncher.getWorkspace();
 
@@ -151,8 +148,7 @@
     }
 
     @Test
-    // TODO(b/197631877) Enable in portrait.
-    // @PortraitLandscape
+    @PortraitLandscape
     public void testDragIconToPage3() {
         Workspace workspace = mLauncher.getWorkspace();
 
@@ -168,8 +164,7 @@
     }
 
     @Test
-    // TODO(b/197631877) Enable in portrait.
-    // @PortraitLandscape
+    @PortraitLandscape
     public void testMultiplePageDragIcon() {
         Workspace workspace = mLauncher.getWorkspace();
 
@@ -215,8 +210,7 @@
     }
 
     @Test
-    // TODO(b/197631877) Enable in portrait.
-    // @PortraitLandscape
+    @PortraitLandscape
     public void testEmptyPageDoesNotGetRemovedIfPagePairIsNotEmpty() {
         Workspace workspace = mLauncher.getWorkspace();
 
@@ -256,8 +250,7 @@
     }
 
     @Test
-    // TODO(b/197631877) Enable in portrait.
-    // @PortraitLandscape
+    @PortraitLandscape
     public void testEmptyPagesGetRemovedIfBothPagesAreEmpty() {
         Workspace workspace = mLauncher.getWorkspace();
 
@@ -284,8 +277,7 @@
     }
 
     @Test
-    // TODO(b/197631877) Enable in portrait.
-    // @PortraitLandscape
+    @PortraitLandscape
     public void testMiddleEmptyPagesGetRemoved() {
         Workspace workspace = mLauncher.getWorkspace();
 
diff --git a/tests/src/com/android/launcher3/util/MultiScalePropertyTest.kt b/tests/src/com/android/launcher3/util/MultiScalePropertyTest.kt
new file mode 100644
index 0000000..c4a8db6
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/MultiScalePropertyTest.kt
@@ -0,0 +1,92 @@
+package com.android.launcher3.util
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** Unit tests for [MultiScalePropertyFactory] */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MultiScalePropertyTest {
+
+    private val received = mutableListOf<Float>()
+
+    private val factory =
+        object : MultiScalePropertyFactory<Int?>("Test") {
+            override fun apply(obj: Int?, value: Float) {
+                received.add(value)
+            }
+        }
+
+    private val p1 = factory.get(1)
+    private val p2 = factory.get(2)
+    private val p3 = factory.get(3)
+
+    @Test
+    fun set_multipleSame_bothAppliedd() {
+        p1.set(null, 0.5f)
+        p1.set(null, 0.5f)
+
+        assertThat(received).containsExactly(0.5f, 0.5f)
+    }
+
+    @Test
+    fun set_differentIndexes_oneValuesNotCounted() {
+        val v1 = 0.5f
+        val v2 = 1.0f
+        p1.set(null, v1)
+        p2.set(null, v2)
+
+        assertThat(received).containsExactly(v1, v1)
+    }
+
+    @Test
+    fun set_onlyOneSetToOne_oneApplied() {
+        p1.set(null, 1.0f)
+
+        assertThat(received).containsExactly(1.0f)
+    }
+
+    @Test
+    fun set_onlyOneLessThanOne_applied() {
+        p1.set(null, 0.5f)
+
+        assertThat(received).containsExactly(0.5f)
+    }
+
+    @Test
+    fun set_differentIndexes_boundToMin() {
+        val v1 = 0.5f
+        val v2 = 0.6f
+        p1.set(null, v1)
+        p2.set(null, v2)
+
+        assertThat(received).containsExactly(v1, v1)
+    }
+
+    @Test
+    fun set_allHigherThanOne_boundToMax() {
+        val v1 = 3.0f
+        val v2 = 2.0f
+        val v3 = 1.0f
+        p1.set(null, v1)
+        p2.set(null, v2)
+        p3.set(null, v3)
+
+        assertThat(received).containsExactly(v1, v1, v1)
+    }
+
+    @Test
+    fun set_differentIndexes_firstModified_aggregationApplied() {
+        val v1 = 0.5f
+        val v2 = 0.6f
+        val v3 = 4f
+        p1.set(null, v1)
+        p2.set(null, v2)
+        p3.set(null, v3)
+
+        assertThat(received).containsExactly(v1, v1, v1 * v2 * v3)
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index c1b0220..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;
@@ -51,16 +51,10 @@
         // Wait for the recycler to populate.
         mLauncher.waitForObjectInContainer(appListRecycler, By.clazz(TextView.class));
         verifyNotFrozen("All apps freeze flags upon opening all apps");
-        mIconHeight = mLauncher.getTestInfo(
-                        TestProtocol.REQUEST_ICON_HEIGHT)
+        mIconHeight = mLauncher.getTestInfo(TestProtocol.REQUEST_ICON_HEIGHT)
                 .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;
@@ -158,7 +152,7 @@
 
                 final UiObject2 appIcon = mLauncher.waitForObjectInContainer(appListRecycler,
                         appIconSelector);
-                return new AllAppsAppIcon(mLauncher, appIcon);
+                return createAppIcon(appIcon);
             } else {
                 return null;
             }
@@ -179,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")) {
@@ -211,7 +207,7 @@
 
     private int getAllAppsScroll() {
         return mLauncher.getTestInfo(
-                        TestProtocol.REQUEST_APPS_LIST_SCROLL_Y)
+                TestProtocol.REQUEST_APPS_LIST_SCROLL_Y)
                 .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
     }
 
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 8fa9e12..bef242c 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
@@ -16,11 +16,8 @@
 
 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;
@@ -30,9 +27,9 @@
 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 {
+public abstract class AppIcon extends Launchable {
 
     AppIcon(LauncherInstrumentation launcher, UiObject2 icon) {
         super(launcher, icon);
@@ -42,6 +39,10 @@
         return By.clazz(TextView.class).text(appName).pkg(launcher.getLauncherPackageName());
     }
 
+    static BySelector getAnyAppIconSelector() {
+        return By.clazz(TextView.class);
+    }
+
     protected abstract Pattern getLongClickEvent();
 
     /**
@@ -49,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()));
         }
     }
@@ -59,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() {
@@ -106,36 +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
-            );
-        }
-    }
-}
\ 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 d84d723..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.
@@ -80,7 +75,8 @@
     protected void goToOverviewUnchecked() {
         switch (mLauncher.getNavigationModel()) {
             case ZERO_BUTTON: {
-                sendDownPointerToEnterOverviewToLauncher();
+                final long downTime = SystemClock.uptimeMillis();
+                sendDownPointerToEnterOverviewToLauncher(downTime);
                 String swipeAndHoldToEnterOverviewActionName =
                         "swiping and holding to enter overview";
                 // If swiping from an app (e.g. Overview is in Background), we pause and hold on
@@ -89,16 +85,17 @@
                 // Workspace state where the below condition is true), there is no need to pause,
                 // and we will not test for an intermediate carousel as one will not exist.
                 if (zeroButtonToOverviewGestureStateTransitionWhileHolding()) {
-                    mLauncher.runToState(this::sendSwipeUpAndHoldToEnterOverviewGestureToLauncher,
+                    mLauncher.runToState(
+                            () -> sendSwipeUpAndHoldToEnterOverviewGestureToLauncher(downTime),
                             OVERVIEW_STATE_ORDINAL, swipeAndHoldToEnterOverviewActionName);
-                    sendUpPointerToEnterOverviewToLauncher();
+                    sendUpPointerToEnterOverviewToLauncher(downTime);
                 } else {
                     // If swiping up from an app to overview, pause on intermediate carousel
                     // until snapshots are visible. No intermediate carousel when swiping from
                     // Home. The task swiped up is not a snapshot but the TaskViewSimulator. If
                     // only a single task exists, no snapshots will be available during swipe up.
                     mLauncher.executeAndWaitForLauncherEvent(
-                            this::sendSwipeUpAndHoldToEnterOverviewGestureToLauncher,
+                            () -> sendSwipeUpAndHoldToEnterOverviewGestureToLauncher(downTime),
                             event -> TestProtocol.PAUSE_DETECTED_MESSAGE.equals(
                                     event.getClassName().toString()),
                             () -> "Pause wasn't detected",
@@ -127,7 +124,7 @@
                         }
                         String upPointerToEnterOverviewActionName =
                                 "sending UP pointer to enter overview";
-                        mLauncher.runToState(this::sendUpPointerToEnterOverviewToLauncher,
+                        mLauncher.runToState(() -> sendUpPointerToEnterOverviewToLauncher(downTime),
                                 OVERVIEW_STATE_ORDINAL, upPointerToEnterOverviewActionName);
                     }
                 }
@@ -153,21 +150,24 @@
     private void expectSwitchToOverviewEvents() {
     }
 
-    private void sendDownPointerToEnterOverviewToLauncher() {
+    private void sendDownPointerToEnterOverviewToLauncher(long downTime) {
         final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
         final int startY = getSwipeStartY();
         final Point start = new Point(centerX, startY);
-        final long downTime = SystemClock.uptimeMillis();
         final LauncherInstrumentation.GestureScope gestureScope =
                 zeroButtonToOverviewGestureStartsInLauncher()
                         ? LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE
                         : LauncherInstrumentation.GestureScope.OUTSIDE_WITH_PILFER;
 
-        mLauncher.sendPointer(
-                downTime, downTime, MotionEvent.ACTION_DOWN, start, gestureScope);
+        mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, start, gestureScope);
+
+        if (!mLauncher.isLauncher3()) {
+            mLauncher.expectEvent(TestProtocol.SEQUENCE_PILFER,
+                    LauncherInstrumentation.EVENT_PILFER_POINTERS);
+        }
     }
 
-    private void sendSwipeUpAndHoldToEnterOverviewGestureToLauncher() {
+    private void sendSwipeUpAndHoldToEnterOverviewGestureToLauncher(long downTime) {
         final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
         final int startY = getSwipeStartY();
         final int swipeHeight = mLauncher.getTestInfo(getSwipeHeightRequestName()).getInt(
@@ -175,7 +175,6 @@
         final Point start = new Point(centerX, startY);
         final Point end =
                 new Point(centerX, startY - swipeHeight - mLauncher.getTouchSlop());
-        final long downTime = SystemClock.uptimeMillis();
         final LauncherInstrumentation.GestureScope gestureScope =
                 zeroButtonToOverviewGestureStartsInLauncher()
                         ? LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE
@@ -190,35 +189,35 @@
                 gestureScope);
     }
 
-    private void sendUpPointerToEnterOverviewToLauncher() {
+    private void sendUpPointerToEnterOverviewToLauncher(long downTime) {
         final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
         final int startY = getSwipeStartY();
         final int swipeHeight = mLauncher.getTestInfo(getSwipeHeightRequestName()).getInt(
                 TestProtocol.TEST_INFO_RESPONSE_FIELD);
         final Point end =
                 new Point(centerX, startY - swipeHeight - mLauncher.getTouchSlop());
-        final long downTime = SystemClock.uptimeMillis();
+
         final LauncherInstrumentation.GestureScope gestureScope =
                 zeroButtonToOverviewGestureStartsInLauncher()
-                        ? LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE
-                        : LauncherInstrumentation.GestureScope.OUTSIDE_WITH_PILFER;
+                        ? LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE_WITHOUT_PILFER
+                        : LauncherInstrumentation.GestureScope.OUTSIDE_WITHOUT_PILFER;
 
         mLauncher.sendPointer(downTime, SystemClock.uptimeMillis(),
                 MotionEvent.ACTION_UP, end, gestureScope);
     }
 
     @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 c22b4da..f063191 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -66,6 +66,7 @@
 import androidx.test.uiautomator.Until;
 
 import com.android.launcher3.ResourceUtils;
+import com.android.launcher3.testing.TestInformationRequest;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.systemui.shared.system.ContextUtils;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -90,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
@@ -105,7 +107,7 @@
     static final Pattern EVENT_TOUCH_DOWN = getTouchEventPattern("ACTION_DOWN");
     static final Pattern EVENT_TOUCH_UP = getTouchEventPattern("ACTION_UP");
     private static final Pattern EVENT_TOUCH_CANCEL = getTouchEventPattern("ACTION_CANCEL");
-    private static final Pattern EVENT_PILFER_POINTERS = Pattern.compile("pilferPointers");
+    static final Pattern EVENT_PILFER_POINTERS = Pattern.compile("pilferPointers");
     static final Pattern EVENT_START = Pattern.compile("start:");
 
     static final Pattern EVENT_TOUCH_DOWN_TIS = getTouchEventPatternTIS("ACTION_DOWN");
@@ -121,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}
@@ -134,7 +136,7 @@
     }
 
     // Base class for launcher containers.
-    static abstract class VisibleContainer {
+    abstract static class VisibleContainer {
         protected final LauncherInstrumentation mLauncher;
 
         protected VisibleContainer(LauncherInstrumentation launcher) {
@@ -301,9 +303,13 @@
     }
 
     Bundle getTestInfo(String request, String arg) {
+        return getTestInfo(request, arg, null);
+    }
+
+    Bundle getTestInfo(String request, String arg, Bundle extra) {
         try (ContentProviderClient client = getContext().getContentResolver()
                 .acquireContentProviderClient(mTestProviderUri)) {
-            return client.call(request, arg, null);
+            return client.call(request, arg, extra);
         } catch (DeadObjectException e) {
             fail("Launcher crashed");
             return null;
@@ -312,6 +318,12 @@
         }
     }
 
+    Bundle getTestInfo(TestInformationRequest request) {
+        Bundle extra = new Bundle();
+        extra.putParcelable(TestProtocol.TEST_INFO_REQUEST_FIELD, request);
+        return getTestInfo(request.getRequestName(), null, extra);
+    }
+
     Insets getTargetInsets() {
         return getTestInfo(TestProtocol.REQUEST_TARGET_INSETS)
                 .getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD);
@@ -489,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();
@@ -515,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) {
@@ -715,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);
@@ -725,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);
@@ -812,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(
@@ -949,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);
     }
 
     /**
@@ -993,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);
         }
     }
 
@@ -1106,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;
@@ -1757,4 +1764,4 @@
         return ResourceUtils.getBoolByName(
                 "config_supportsRoundedCornersOnWindows", resources, false);
     }
-}
\ No newline at end of file
+}
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 16987e9..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;
@@ -41,6 +40,7 @@
 import androidx.test.uiautomator.Until;
 
 import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.WorkspaceCellCenterRequest;
 
 import java.util.List;
 import java.util.function.Supplier;
@@ -78,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")) {
@@ -88,7 +88,7 @@
             final int windowCornerRadius = (int) Math.ceil(mLauncher.getWindowCornerRadius());
             final int startY = deviceHeight - Math.max(bottomGestureMargin, windowCornerRadius) - 1;
             final int swipeHeight = mLauncher.getTestInfo(
-                            TestProtocol.REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT)
+                    TestProtocol.REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT)
                     .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
             LauncherInstrumentation.log(
                     "switchToAllApps: deviceHeight = " + deviceHeight + ", startY = " + startY
@@ -105,7 +105,7 @@
 
             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
                     "swiped to all apps")) {
-                return new AllApps(mLauncher);
+                return new HomeAllApps(mLauncher);
             }
         }
     }
@@ -117,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();
@@ -135,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,
@@ -178,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.
+     *                  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.");
@@ -192,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,
@@ -218,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.
      */
@@ -241,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,19 +262,22 @@
      * 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 appIcon to be uninstalled.
+     * @param homeAppIcon to be uninstalled.
+     * @param launcher              the root TAPL instrumentation object of {@link
+     *                              LauncherInstrumentation} type.
+     * @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);
 
@@ -305,6 +303,23 @@
     }
 
     /**
+     * Get cell layout's grids size. The return point's x and y values are the cell counts in X and
+     * Y directions respectively, not the values in pixels.
+     */
+    public Point getIconGridDimensions() {
+        int[] countXY = mLauncher.getTestInfo(
+                TestProtocol.REQUEST_WORKSPACE_CELL_LAYOUT_SIZE).getIntArray(
+                TestProtocol.TEST_INFO_RESPONSE_FIELD);
+        return new Point(countXY[0], countXY[1]);
+    }
+
+    static Point getCellCenter(LauncherInstrumentation launcher, int cellX, int cellY) {
+        return launcher.getTestInfo(WorkspaceCellCenterRequest.builder().setCellX(
+                cellX).setCellY(cellY).build()).getParcelable(
+                TestProtocol.TEST_INFO_RESPONSE_FIELD);
+    }
+
+    /**
      * Finds folder icons in the current workspace.
      *
      * @return a list of folder icons.
@@ -315,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(
@@ -384,15 +374,19 @@
         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;
 
             // Since the destination can be on another page, we need to drag to the edge first
             // until we reach the target page
             while (targetDest.x > displayX || targetDest.x < 0) {
-                int edgeX = targetDest.x > 0 ? displayX : 0;
+                // TODO: b/219919285
+                int edgeX = targetDest.x > 0 ? displayX - 1 : 1;
                 Point screenEdge = new Point(edgeX, targetDest.y);
                 Point finalDragStart = dragStart;
                 executeAndWaitForPageScroll(launcher,
diff --git a/tests/tapl/com/android/launcher3/tapl/WorkspaceAppIcon.java b/tests/tapl/com/android/launcher3/tapl/WorkspaceAppIcon.java
index 5f4e469..114e6a5 100644
--- a/tests/tapl/com/android/launcher3/tapl/WorkspaceAppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/WorkspaceAppIcon.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.tapl;
 
+import android.graphics.Point;
+
 import androidx.test.uiautomator.UiObject2;
 
 import java.util.regex.Pattern;
@@ -23,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);
@@ -33,4 +35,9 @@
     protected Pattern getLongClickEvent() {
         return Workspace.LONG_CLICK_EVENT;
     }
+
+    boolean isInCell(int cellX, int cellY) {
+        final Point center = Workspace.getCellCenter(mLauncher, cellX, cellY);
+        return mObject.getParent().getVisibleBounds().contains(center.x, center.y);
+    }
 }
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();
+}