Merge "Switch pip to fixed-rotation in shell transitions"
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 2848b0f..11d75c7 100644
--- a/quickstep/res/layout/taskbar_all_apps.xml
+++ b/quickstep/res/layout/taskbar_all_apps.xml
@@ -30,6 +30,10 @@
         android:theme="?attr/allAppsTheme">
 
         <include
+            layout="@layout/all_apps_bottom_sheet_background"
+            android:visibility="gone" />
+
+        <include
             layout="@layout/all_apps_rv_layout"
             android:visibility="gone" />
 
@@ -42,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/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 49b2cc5..3eb1935e 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3;
 
+import static android.provider.Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING;
 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_NONE;
 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
 
@@ -75,6 +76,7 @@
 import android.os.Looper;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.util.Pair;
 import android.util.Size;
 import android.view.SurfaceControl;
@@ -683,6 +685,18 @@
         appAnimator.addListener(floatingView);
         appAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
+            public void onAnimationStart(Animator animation) {
+                LauncherTaskbarUIController taskbarController = mLauncher.getTaskbarUIController();
+                if (taskbarController != null && taskbarController.shouldShowEdu()) {
+                    // LAUNCHER_TASKBAR_EDUCATION_SHOWING is set to true here, when the education
+                    // flow is about to start, to avoid a race condition with other components
+                    // that would show something else to the user as soon as the app is opened.
+                    Settings.Secure.putInt(mLauncher.getContentResolver(),
+                            LAUNCHER_TASKBAR_EDUCATION_SHOWING, 1);
+                }
+            }
+
+            @Override
             public void onAnimationEnd(Animator animation) {
                 if (v instanceof BubbleTextView) {
                     ((BubbleTextView) v).setStayPressed(false);
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/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 4132c68..0f91aa2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -216,9 +216,7 @@
      * Starts the taskbar education flow, if the user hasn't seen it yet.
      */
     public void showEdu() {
-        if (!FeatureFlags.ENABLE_TASKBAR_EDU.get()
-                || Utilities.IS_RUNNING_IN_TEST_HARNESS
-                || mLauncher.getOnboardingPrefs().getBoolean(OnboardingPrefs.TASKBAR_EDU_SEEN)) {
+        if (!shouldShowEdu()) {
             return;
         }
         mLauncher.getOnboardingPrefs().markChecked(OnboardingPrefs.TASKBAR_EDU_SEEN);
@@ -227,6 +225,15 @@
     }
 
     /**
+     * Whether the taskbar education should be shown.
+     */
+    public boolean shouldShowEdu() {
+        return FeatureFlags.ENABLE_TASKBAR_EDU.get()
+                && !Utilities.IS_RUNNING_IN_TEST_HARNESS
+                && !mLauncher.getOnboardingPrefs().getBoolean(OnboardingPrefs.TASKBAR_EDU_SEEN);
+    }
+
+    /**
      * Manually ends the taskbar education flow.
      */
     public void hideEdu() {
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/TaskbarEduView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduView.java
index 8525427..89d67be 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduView.java
@@ -20,6 +20,7 @@
 import android.animation.PropertyValuesHolder;
 import android.content.Context;
 import android.graphics.Rect;
+import android.provider.Settings;
 import android.util.AttributeSet;
 import android.util.Pair;
 import android.view.View;
@@ -92,6 +93,14 @@
         getPopupContainer().addView(this, 1);
     }
 
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING, 0);
+    }
+
     /** Show the Education flow. */
     public void show() {
         attachToContainer();
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/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 20762b9..335b637 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -275,6 +275,9 @@
             // taskbar, we use an OnLongClickListener on TaskbarView instead.
             return false;
         }
+        if (!canCurrentlyManuallyUnstash()) {
+            return false;
+        }
         if (updateAndAnimateIsManuallyStashedInApp(false)) {
             mControllers.taskbarActivityContext.getDragLayer().performHapticFeedback(LONG_PRESS);
             return true;
@@ -283,6 +286,16 @@
     }
 
     /**
+     * Returns whether taskbar will unstash when long pressing it based on the current state. The
+     * only time this is true is if the user is in an app and the taskbar is only stashed because
+     * the user previously long pressed to manually stash (not due to other reasons like IME).
+     */
+    private boolean canCurrentlyManuallyUnstash() {
+        return (mState & (FLAG_IN_APP | FLAGS_STASHED_IN_APP))
+                == (FLAG_IN_APP | FLAG_STASHED_IN_APP_MANUAL);
+    }
+
+    /**
      * Updates whether we should stash the taskbar when in apps, and animates to the changed state.
      * @return Whether we started an animation to either be newly stashed or unstashed.
      */
@@ -423,6 +436,11 @@
             // Already unstashed, no need to hint in that direction.
             return;
         }
+        if (!canCurrentlyManuallyUnstash()) {
+            // If any other flags are causing us to be stashed, long press won't cause us to
+            // unstash, so don't hint that it will.
+            return;
+        }
         mTaskbarStashedHandleHintScale.animateToValue(
                 animateForward ? UNSTASHED_TASKBAR_HANDLE_HINT_SCALE : 1)
                 .setDuration(TASKBAR_HINT_STASH_DURATION).start();
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 62f1fa5..8b6831b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -215,7 +215,7 @@
         PendingAnimation setter = new PendingAnimation(100);
         Rect hotseatPadding = launcherDp.getHotseatLayoutPadding(mActivity);
         float scaleUp = ((float) launcherDp.iconSizePx) / mActivity.getDeviceProfile().iconSizePx;
-        int borderSpacing = launcherDp.cellLayoutBorderSpacePx.x;
+        int borderSpacing = launcherDp.hotseatBorderSpace;
         int hotseatCellSize = DeviceProfile.calculateCellWidth(
                 launcherDp.availableWidthPx - hotseatPadding.left - hotseatPadding.right,
                 borderSpacing,
@@ -253,9 +253,9 @@
 
             float hotseatIconCenter = hotseatPadding.left
                     + (hotseatCellSize + borderSpacing) * positionInHotseat
-                    + hotseatCellSize / 2;
+                    + hotseatCellSize / 2f;
 
-            float childCenter = (child.getLeft() + child.getRight()) / 2;
+            float childCenter = (child.getLeft() + child.getRight()) / 2f;
             setter.setFloat(child, ICON_TRANSLATE_X, hotseatIconCenter - childCenter, LINEAR);
 
             setter.setFloat(child, SCALE_PROPERTY, scaleUp, LINEAR);
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/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
index ae2583b..4c570f1 100644
--- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -5,6 +5,8 @@
 import android.graphics.Rect;
 import android.os.Bundle;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.testing.TestInformationHandler;
 import com.android.launcher3.testing.TestProtocol;
@@ -21,7 +23,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_ALL_APPS_TO_OVERVIEW_SWIPE_HEIGHT: {
@@ -82,7 +84,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/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index b8a885b..5e298f4 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -23,9 +23,9 @@
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
 
 import android.app.ActivityManager;
+import android.app.ActivityOptions;
 import android.content.Context;
 import android.content.Intent;
-import android.os.Bundle;
 import android.os.SystemProperties;
 import android.util.Log;
 import android.view.RemoteAnimationTarget;
@@ -198,9 +198,10 @@
             RemoteTransitionCompat transition = new RemoteTransitionCompat(mCallbacks,
                     mController != null ? mController.getController() : null,
                     mCtx.getIApplicationThread());
-            Bundle options = ActivityOptionsCompat.makeRemoteTransition(transition)
-                    .setTransientLaunch().toBundle();
-            UI_HELPER_EXECUTOR.execute(() -> mCtx.startActivity(intent, options));
+            final ActivityOptions options = ActivityOptionsCompat.makeRemoteTransition(transition)
+                    .setTransientLaunch();
+            options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_RECENTS_ANIMATION, eventTime);
+            UI_HELPER_EXECUTOR.execute(() -> mCtx.startActivity(intent, options.toBundle()));
         } else {
             UI_HELPER_EXECUTOR.execute(() -> ActivityManagerWrapper.getInstance()
                     .startRecentsActivity(intent, eventTime, mCallbacks, null, null));
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index ad52a66..12a638a 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -412,9 +412,8 @@
                     atomInfo.getFolderIcon().getToLabelState().getNumber() /* toState */,
                     atomInfo.getFolderIcon().getLabelInfo() /* edittext */,
                     getCardinality(atomInfo) /* cardinality */,
-                    getFeatures(atomInfo) /* features */
-                    // TODO(b/217753033) : Add SearchAttributes field after necessary approval
-                    // getSearchAttributes(atomInfo) /* searchAttributes */
+                    getFeatures(atomInfo) /* features */,
+                    getSearchAttributes(atomInfo) /* searchAttributes */
             );
         }
     }
@@ -572,9 +571,14 @@
     }
 
     private static int getSearchAttributes(LauncherAtom.ItemInfo info) {
-        if (info.getContainerInfo().getExtendedContainers().getDeviceSearchResultContainer()
-                .hasSearchAttributes()) {
-            return searchAttributesToInt(info.getContainerInfo().getExtendedContainers()
+        ContainerInfo containerInfo = info.getContainerInfo();
+        if (containerInfo.getContainerCase() == EXTENDED_CONTAINERS
+                && containerInfo.getExtendedContainers().getContainerCase()
+                == DEVICE_SEARCH_RESULT_CONTAINER
+                && containerInfo.getExtendedContainers()
+                .getDeviceSearchResultContainer().hasSearchAttributes()
+        ) {
+            return searchAttributesToInt(containerInfo.getExtendedContainers()
                     .getDeviceSearchResultContainer().getSearchAttributes());
         }
         return 0;
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/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/DigitalWellBeingToast.java b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
index 3757cd9..27a748d 100644
--- a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
+++ b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
@@ -144,7 +144,6 @@
 
     public void initialize(Task task) {
         mTask = task;
-
         THREAD_POOL_EXECUTOR.execute(() -> {
             final AppUsageLimit usageLimit = mLauncherApps.getAppUsageLimit(
                     task.getTopComponent().getPackageName(),
@@ -321,7 +320,7 @@
                 mTaskView.getThumbnail().getLayoutParams()).bottomMargin;
         PagedOrientationHandler orientationHandler = mTaskView.getPagedOrientationHandler();
         Pair<Float, Float> translations = orientationHandler
-                .setDwbLayoutParamsAndGetTranslations(mTaskView.getMeasuredWidth(),
+                .getDwbLayoutTranslations(mTaskView.getMeasuredWidth(),
                         mTaskView.getMeasuredHeight(), mStagedSplitBounds, deviceProfile,
                         mTaskView.getThumbnails(), mTask.key.id, mBanner);
         mSplitOffsetTranslationX = translations.first;
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index 3ddec26..af9f818 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -242,6 +242,7 @@
         mIconView2.setDrawableSize(iconDrawableSize, iconDrawableSize);
         mIconView2.setRotation(getPagedOrientationHandler().getDegreesRotated());
         updateIconPlacement();
+        updateSecondaryDwbPlacement();
     }
 
     private void updateIconPlacement() {
@@ -258,6 +259,13 @@
                 isRtl, deviceProfile, mSplitBoundsConfig);
     }
 
+    private void updateSecondaryDwbPlacement() {
+        if (mSecondaryTask == null) {
+            return;
+        }
+        mDigitalWellBeingToast2.initialize(mSecondaryTask);
+    }
+
     @Override
     protected void updateSnapshotRadius() {
         super.updateSnapshotRadius();
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 2a2d1dd..c11a8bb 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);
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index e26bf73..cdb8082 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -897,6 +897,7 @@
         LayoutParams iconParams = (LayoutParams) mIconView.getLayoutParams();
         orientationHandler.setIconAndSnapshotParams(mIconView, taskIconMargin, taskIconHeight,
                 snapshotParams, isRtl);
+        updateDwbPlacement();
         mSnapshotView.setLayoutParams(snapshotParams);
         iconParams.width = iconParams.height = taskIconHeight;
         mIconView.setLayoutParams(iconParams);
@@ -909,6 +910,10 @@
         mSnapshotView.getTaskOverlay().updateOrientationState(orientationState);
     }
 
+    private void updateDwbPlacement() {
+        mDigitalWellBeingToast.initialize(mTask);
+    }
+
     /**
      * Returns whether the task is part of overview grid and not being focused.
      */
diff --git a/res/color-night-v31/accent_ripple_color.xml b/res/color-night-v31/accent_ripple_color.xml
new file mode 100644
index 0000000..cb149d6
--- /dev/null
+++ b/res/color-night-v31/accent_ripple_color.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@android:color/system_accent1_300"/>
+</selector>
\ No newline at end of file
diff --git a/res/color-night/accent_ripple_color.xml b/res/color-night/accent_ripple_color.xml
new file mode 100644
index 0000000..4a37b00
--- /dev/null
+++ b/res/color-night/accent_ripple_color.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="#53BCAC"/>
+</selector>
\ No newline at end of file
diff --git a/res/color-v31/accent_ripple_color.xml b/res/color-v31/accent_ripple_color.xml
new file mode 100644
index 0000000..a996228
--- /dev/null
+++ b/res/color-v31/accent_ripple_color.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@android:color/system_accent2_50"/>
+</selector>
\ No newline at end of file
diff --git a/res/color/accent_ripple_color.xml b/res/color/accent_ripple_color.xml
new file mode 100644
index 0000000..697f415
--- /dev/null
+++ b/res/color/accent_ripple_color.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="#CDFAF1"/>
+</selector>
\ No newline at end of file
diff --git a/res/drawable/all_apps_tabs_background.xml b/res/drawable/all_apps_tabs_background.xml
index aea2e7a..8471cd4 100644
--- a/res/drawable/all_apps_tabs_background.xml
+++ b/res/drawable/all_apps_tabs_background.xml
@@ -13,23 +13,36 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:enterFadeDuration="100">
-    <item
-        android:id="@+id/unselected"
-        android:state_selected="false">
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="@color/accent_ripple_color">
+
+    <item android:id="@android:id/mask">
         <shape android:shape="rectangle">
             <corners android:radius="@dimen/all_apps_header_pill_corner_radius" />
-            <solid android:color="@color/all_apps_tabs_background" />
+            <solid android:color="@color/accent_ripple_color" />
         </shape>
     </item>
 
-    <item
-        android:id="@+id/selected"
-        android:state_selected="true">
-        <shape android:shape="rectangle">
-            <corners android:radius="@dimen/all_apps_header_pill_corner_radius" />
-            <solid android:color="@color/all_apps_tab_background_selected" />
-        </shape>
+    <item>
+        <selector android:enterFadeDuration="100">
+            <item
+                android:id="@+id/unselected"
+                android:state_selected="false">
+                <shape android:shape="rectangle">
+                    <corners android:radius="@dimen/all_apps_header_pill_corner_radius" />
+                    <solid android:color="@color/all_apps_tabs_background" />
+                </shape>
+            </item>
+
+            <item
+                android:id="@+id/selected"
+                android:state_selected="true">
+                <shape android:shape="rectangle">
+                    <corners android:radius="@dimen/all_apps_header_pill_corner_radius" />
+                    <solid android:color="@color/all_apps_tab_background_selected" />
+                </shape>
+            </item>
+        </selector>
     </item>
-</selector>
\ No newline at end of file
+
+</ripple>
\ No newline at end of file
diff --git a/res/drawable/personal_work_tabs_ripple.xml b/res/drawable/personal_work_tabs_ripple.xml
deleted file mode 100644
index 2e57b80..0000000
--- a/res/drawable/personal_work_tabs_ripple.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<ripple xmlns:android="http://schemas.android.com/apk/res/android"
-    android:color="?android:attr/colorControlHighlight">
-    <shape android:shape="rectangle">
-        <solid android:color="@android:color/transparent" />
-        <corners android:radius="@dimen/all_apps_header_pill_corner_radius" />
-    </shape>
-</ripple>
\ No newline at end of file
diff --git a/res/layout/all_apps.xml b/res/layout/all_apps.xml
index 6df6212..2ac7e63 100644
--- a/res/layout/all_apps.xml
+++ b/res/layout/all_apps.xml
@@ -26,6 +26,10 @@
     android:saveEnabled="false">
 
     <include
+        layout="@layout/all_apps_bottom_sheet_background"
+        android:visibility="gone" />
+
+    <include
         layout="@layout/all_apps_rv_layout"
         android:visibility="gone" />
 
diff --git a/res/layout/all_apps_bottom_sheet_background.xml b/res/layout/all_apps_bottom_sheet_background.xml
new file mode 100644
index 0000000..ad10d68
--- /dev/null
+++ b/res/layout/all_apps_bottom_sheet_background.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/bottom_sheet_background"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@drawable/bg_all_apps_bottom_sheet">
+
+    <View
+        android:id="@+id/bottom_sheet_handle_area"
+        android:layout_width="match_parent"
+        android:layout_height="34dp" />
+
+    <View
+        android:id="@+id/bottom_sheet_handle"
+        android:layout_width="48dp"
+        android:layout_height="2dp"
+        android:layout_gravity="center_horizontal"
+        android:layout_marginTop="16dp"
+        android:layout_marginBottom="16dp"
+        android:background="?android:attr/textColorSecondary" />
+</FrameLayout>
diff --git a/res/layout/secondary_launcher.xml b/res/layout/secondary_launcher.xml
index 3ccd4f2..0fe05ee 100644
--- a/res/layout/secondary_launcher.xml
+++ b/res/layout/secondary_launcher.xml
@@ -42,7 +42,7 @@
         android:onClick="onAppsButtonClicked" />
 
     <view
-        class="com.android.launcher3.allapps.ActivityAllAppsContainerView"
+        class="com.android.launcher3.allapps.SecondaryLauncherAllAppsContainerView"
         android:id="@+id/apps_view"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
@@ -56,6 +56,10 @@
         android:visibility="invisible" >
 
         <include
+            layout="@layout/all_apps_bottom_sheet_background"
+            android:visibility="gone" />
+
+        <include
             layout="@layout/all_apps_rv_layout"
             android:visibility="gone" />
 
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 661aa00..fcd147f 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();
     }
@@ -1782,6 +1783,11 @@
         return mWorkspaceLoading;
     }
 
+    @Override
+    public boolean isBindingItems() {
+        return mWorkspaceLoading;
+    }
+
     private void setWorkspaceLoading(boolean value) {
         mWorkspaceLoading = value;
     }
@@ -2401,6 +2407,10 @@
                     } else {
                         Log.d(TAG, desc);
                         getModelWriter().deleteItemFromDatabase(item);
+                        if (TestProtocol.sDebugTracing) {
+                            Log.d(TestProtocol.MISSING_PROMISE_ICON,
+                                    TAG + "bindItems failed for item=" + item);
+                        }
                         continue;
                     }
                 }
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/SessionCommitReceiver.java b/src/com/android/launcher3/SessionCommitReceiver.java
index 558538c..b81637f 100644
--- a/src/com/android/launcher3/SessionCommitReceiver.java
+++ b/src/com/android/launcher3/SessionCommitReceiver.java
@@ -24,12 +24,14 @@
 import android.content.pm.PackageManager;
 import android.os.UserHandle;
 import android.text.TextUtils;
+import android.util.Log;
 
 import androidx.annotation.WorkerThread;
 
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.model.ItemInstallQueue;
 import com.android.launcher3.pm.InstallSessionHelper;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.Executors;
 
 /**
@@ -51,6 +53,9 @@
     private static void processIntent(Context context, Intent intent) {
         if (!isEnabled(context)) {
             // User has decided to not add icons on homescreen.
+            if (TestProtocol.sDebugTracing) {
+                Log.d(TestProtocol.MISSING_PROMISE_ICON, LOG + " not enabled");
+            }
             return;
         }
 
@@ -59,6 +64,9 @@
         if (!PackageInstaller.ACTION_SESSION_COMMITTED.equals(intent.getAction())
                 || info == null || user == null) {
             // Invalid intent.
+            if (TestProtocol.sDebugTracing) {
+                Log.d(TestProtocol.MISSING_PROMISE_ICON, LOG + " invalid intent");
+            }
             return;
         }
 
@@ -68,6 +76,15 @@
                 || info.getInstallReason() != PackageManager.INSTALL_REASON_USER
                 || packageInstallerCompat.promiseIconAddedForId(info.getSessionId())) {
             packageInstallerCompat.removePromiseIconId(info.getSessionId());
+            if (TestProtocol.sDebugTracing) {
+                int id = info.getSessionId();
+                Log.d(TestProtocol.MISSING_PROMISE_ICON, LOG
+                        + ", TextUtils.isEmpty=" + TextUtils.isEmpty(info.getAppPackageName())
+                        + ", info.getInstallReason()=" + info.getInstallReason()
+                        + ", INSTALL_REASON_USER=" + PackageManager.INSTALL_REASON_USER
+                        + ", icon added=" + packageInstallerCompat.promiseIconAddedForId(id)
+                );
+            }
             return;
         }
 
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 2b2c7c5..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";
 
@@ -110,6 +112,8 @@
     private AllAppsPagedView mViewPager;
 
     protected FloatingHeaderView mHeader;
+    private View mBottomSheetBackground;
+    private View mBottomSheetHandleArea;
 
     protected boolean mUsingTabs;
     private boolean mHasWorkApps;
@@ -146,8 +150,7 @@
         mNavBarScrimPaint.setColor(Themes.getAttrColor(context, R.attr.allAppsNavBarScrimColor));
 
         mAllAppsStore.addUpdateListener(this::onAppsUpdated);
-
-        updateBackground(mActivityContext.getDeviceProfile());
+        mActivityContext.addOnDeviceProfileChangeListener(this);
     }
 
     /** Creates the adapter provider for the main section. */
@@ -222,10 +225,8 @@
         updateBackground(dp);
     }
 
-    private void updateBackground(DeviceProfile deviceProfile) {
-        setBackground(deviceProfile.isTablet
-                ? getContext().getDrawable(R.drawable.bg_all_apps_bottom_sheet)
-                : null);
+    protected void updateBackground(DeviceProfile deviceProfile) {
+        mBottomSheetBackground.setVisibility(deviceProfile.isTablet ? View.VISIBLE : View.GONE);
     }
 
     private void onAppsUpdated() {
@@ -253,7 +254,9 @@
         if (!mActivityContext.getDragLayer().isEventOverView(this, ev)) {
             return true;
         }
-        // TODO(b/216203409) Support dragging down from bottom sheet divider, if present.
+        if (mActivityContext.getDragLayer().isEventOverView(mBottomSheetHandleArea, ev)) {
+            return true;
+        }
         AllAppsRecyclerView rv = getActiveRecyclerView();
         if (rv == null) {
             return true;
@@ -375,6 +378,11 @@
 
         mHeader = findViewById(R.id.all_apps_header);
         rebindAdapters(true /* force */);
+
+        mBottomSheetBackground = findViewById(R.id.bottom_sheet_background);
+        updateBackground(mActivityContext.getDeviceProfile());
+
+        mBottomSheetHandleArea = findViewById(R.id.bottom_sheet_handle_area);
     }
 
     @Override
@@ -392,7 +400,6 @@
         }
 
         MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
-        mlp.topMargin = grid.isTablet ? insets.top : 0;
         int leftRightMargin = grid.allAppsLeftRightMargin;
         mlp.leftMargin = insets.left + leftRightMargin;
         mlp.rightMargin = insets.right + leftRightMargin;
@@ -401,7 +408,7 @@
         if (grid.isVerticalBarLayout()) {
             setPadding(grid.workspacePadding.left, 0, grid.workspacePadding.right, 0);
         } else {
-            setPadding(0, 0, 0, 0);
+            setPadding(0, grid.isTablet ? insets.top : 0, 0, 0);
         }
 
         InsettableFrameLayout.dispatchInsets(this, insets);
@@ -576,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/allapps/SecondaryLauncherAllAppsContainerView.java b/src/com/android/launcher3/allapps/SecondaryLauncherAllAppsContainerView.java
new file mode 100644
index 0000000..0719c43
--- /dev/null
+++ b/src/com/android/launcher3/allapps/SecondaryLauncherAllAppsContainerView.java
@@ -0,0 +1,45 @@
+/*
+ * 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.allapps;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.secondarydisplay.SecondaryDisplayLauncher;
+
+/**
+ * AllAppsContainerView for secondary launcher
+ */
+public class SecondaryLauncherAllAppsContainerView extends
+        ActivityAllAppsContainerView<SecondaryDisplayLauncher> {
+
+    public SecondaryLauncherAllAppsContainerView(Context context) {
+        this(context, null);
+    }
+
+    public SecondaryLauncherAllAppsContainerView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public SecondaryLauncherAllAppsContainerView(Context context, AttributeSet attrs,
+            int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    @Override
+    protected void updateBackground(DeviceProfile deviceProfile) {}
+}
diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java
index 7793b16..2b621bd 100644
--- a/src/com/android/launcher3/folder/FolderNameProvider.java
+++ b/src/com/android/launcher3/folder/FolderNameProvider.java
@@ -27,14 +27,12 @@
 import com.android.launcher3.model.AllAppsList;
 import com.android.launcher3.model.BaseModelUpdateTask;
 import com.android.launcher3.model.BgDataModel;
-import com.android.launcher3.model.StringCache;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.util.IntSparseArrayMap;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.ResourceBasedOverride;
-import com.android.launcher3.views.ActivityContext;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -109,9 +107,7 @@
         Set<UserHandle> users = workspaceItemInfos.stream().map(w -> w.user)
                 .collect(Collectors.toSet());
         if (users.size() == 1 && !users.contains(Process.myUserHandle())) {
-            StringCache cache = ActivityContext.lookupContext(context).getStringCache();
-            String workFolderName = cache != null
-                    ? cache.workFolderName : context.getString(R.string.work_folder_name);
+            String workFolderName = context.getString(R.string.work_folder_name);
             setAsLastSuggestion(nameInfos, workFolderName);
         }
 
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index a13fa55..91fb44e 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -22,6 +22,7 @@
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.os.UserHandle;
+import android.util.Log;
 import android.util.LongSparseArray;
 import android.util.Pair;
 
@@ -39,6 +40,7 @@
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.pm.InstallSessionHelper;
 import com.android.launcher3.pm.PackageInstallInfo;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.GridOccupancy;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSet;
@@ -82,11 +84,19 @@
                         item.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
                     // Short-circuit this logic if the icon exists somewhere on the workspace
                     if (shortcutExists(dataModel, item.getIntent(), item.user)) {
+                        if (TestProtocol.sDebugTracing) {
+                            Log.d(TestProtocol.MISSING_PROMISE_ICON,
+                                    LOG + " Item already on workspace.");
+                        }
                         continue;
                     }
 
                     // b/139663018 Short-circuit this logic if the icon is a system app
                     if (PackageManagerHelper.isSystemApp(app.getContext(), item.getIntent())) {
+                        if (TestProtocol.sDebugTracing) {
+                            Log.d(TestProtocol.MISSING_PROMISE_ICON,
+                                    LOG + " Item is a system app.");
+                        }
                         continue;
                     }
                 }
@@ -126,6 +136,9 @@
                     String packageName = item.getTargetComponent() != null
                             ? item.getTargetComponent().getPackageName() : null;
                     if (packageName == null) {
+                        if (TestProtocol.sDebugTracing) {
+                            Log.d(TestProtocol.MISSING_PROMISE_ICON, LOG + " Null packageName.");
+                        }
                         continue;
                     }
                     SessionInfo sessionInfo = packageInstaller.getActiveSessionInfo(item.user,
@@ -134,6 +147,9 @@
                     if (!packageInstaller.verifySessionInfo(sessionInfo)) {
                         FileLog.d(LOG, "Item info failed session info verification. "
                                 + "Skipping : " + workspaceInfo);
+                        if (TestProtocol.sDebugTracing) {
+                            Log.d(TestProtocol.MISSING_PROMISE_ICON, LOG + "Failed verification.");
+                        }
                         continue;
                     }
 
@@ -144,6 +160,9 @@
                     if (sessionInfo == null) {
                         if (!hasActivity) {
                             // Session was cancelled, do not add.
+                            if (TestProtocol.sDebugTracing) {
+                                Log.d(TestProtocol.MISSING_PROMISE_ICON, LOG + "Session cancelled");
+                            }
                             continue;
                         }
                     } else {
@@ -163,6 +182,9 @@
                             // workspace items as promise icons. At this point we now have the
                             // correct intent to compare against existing workspace icons.
                             // Icon already exists on the workspace and should not be auto-added.
+                            if (TestProtocol.sDebugTracing) {
+                                Log.d(TestProtocol.MISSING_PROMISE_ICON, LOG + "shortcutExists");
+                            }
                             continue;
                         }
 
diff --git a/src/com/android/launcher3/model/ItemInstallQueue.java b/src/com/android/launcher3/model/ItemInstallQueue.java
index 217f523..5a220f7 100644
--- a/src/com/android/launcher3/model/ItemInstallQueue.java
+++ b/src/com/android/launcher3/model/ItemInstallQueue.java
@@ -49,6 +49,7 @@
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.shortcuts.ShortcutRequest;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.PersistedItemArray;
 import com.android.launcher3.util.Preconditions;
@@ -118,10 +119,18 @@
         Launcher launcher = Launcher.ACTIVITY_TRACKER.getCreatedActivity();
         if (launcher == null) {
             // Launcher not loaded
+            if (TestProtocol.sDebugTracing) {
+                Log.d(TestProtocol.MISSING_PROMISE_ICON,
+                        LOG + " flushQueueInBackground launcher not loaded");
+            }
             return;
         }
         ensureQueueLoaded();
         if (mItems.isEmpty()) {
+            if (TestProtocol.sDebugTracing) {
+                Log.d(TestProtocol.MISSING_PROMISE_ICON,
+                        LOG + " flushQueueInBackground no items to load");
+            }
             return;
         }
 
@@ -131,6 +140,10 @@
 
         // Add the items and clear queue
         if (!installQueue.isEmpty()) {
+            if (TestProtocol.sDebugTracing) {
+                Log.d(TestProtocol.MISSING_PROMISE_ICON,
+                        LOG + " flushQueueInBackground launcher addAndBindAddedWorkspaceItems");
+            }
             // add log
             launcher.getModel().addAndBindAddedWorkspaceItems(installQueue);
         }
@@ -191,6 +204,10 @@
         // Queue the item up for adding if launcher has not loaded properly yet
         MODEL_EXECUTOR.post(() -> {
             Pair<ItemInfo, Object> itemInfo = info.getItemInfo(mContext);
+            if (TestProtocol.sDebugTracing) {
+                Log.d(TestProtocol.MISSING_PROMISE_ICON, LOG + " queuePendingShortcutInfo"
+                        + ", itemInfo=" + itemInfo);
+            }
             if (itemInfo == null) {
                 FileLog.d(LOG,
                         "Adding PendingInstallShortcutInfo with no attached info to queue.",
diff --git a/src/com/android/launcher3/pm/InstallSessionHelper.java b/src/com/android/launcher3/pm/InstallSessionHelper.java
index 4b86f65..618f926 100644
--- a/src/com/android/launcher3/pm/InstallSessionHelper.java
+++ b/src/com/android/launcher3/pm/InstallSessionHelper.java
@@ -27,6 +27,7 @@
 import android.os.Process;
 import android.os.UserHandle;
 import android.text.TextUtils;
+import android.util.Log;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
@@ -38,6 +39,7 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.model.ItemInstallQueue;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.MainThreadInitializedObject;
@@ -142,6 +144,16 @@
         if (sessionInfo == null
                 || sessionInfo.getInstallerPackageName() == null
                 || TextUtils.isEmpty(sessionInfo.getAppPackageName())) {
+            if (TestProtocol.sDebugTracing) {
+                Log.d(TestProtocol.MISSING_PROMISE_ICON, LOG + " verify"
+                        + ", info=" + (sessionInfo == null)
+                        + ", info install name" + (sessionInfo == null
+                                ? null
+                                : sessionInfo.getInstallerPackageName())
+                        + ", empty pkg name" + TextUtils.isEmpty((sessionInfo == null
+                                ? null
+                                : sessionInfo.getAppPackageName())));
+            }
             return null;
         }
         String pkg = sessionInfo.getInstallerPackageName();
@@ -211,6 +223,14 @@
      */
     @WorkerThread
     void tryQueuePromiseAppIcon(PackageInstaller.SessionInfo sessionInfo) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.MISSING_PROMISE_ICON, LOG + " tryQueuePromiseAppIcon"
+                    + ", FeatureFlags=" + FeatureFlags.PROMISE_APPS_NEW_INSTALLS.get()
+                    + ", SessionCommitReceiveEnabled" + SessionCommitReceiver.isEnabled(mAppContext)
+                    + ", verifySessionInfo(sessionInfo)=" + verifySessionInfo(sessionInfo)
+                    + ", !promiseIconAdded=" + (sessionInfo != null
+                    && !promiseIconAddedForId(sessionInfo.getSessionId())));
+        }
         if (FeatureFlags.PROMISE_APPS_NEW_INSTALLS.get()
                 && SessionCommitReceiver.isEnabled(mAppContext)
                 && verifySessionInfo(sessionInfo)
@@ -227,6 +247,20 @@
     }
 
     public boolean verifySessionInfo(PackageInstaller.SessionInfo sessionInfo) {
+        if (TestProtocol.sDebugTracing) {
+            boolean appNotInstalled = sessionInfo == null
+                    || !new PackageManagerHelper(mAppContext)
+                    .isAppInstalled(sessionInfo.getAppPackageName(), getUserHandle(sessionInfo));
+            boolean labelNotEmpty = sessionInfo != null
+                    && !TextUtils.isEmpty(sessionInfo.getAppLabel());
+            Log.d(TestProtocol.MISSING_PROMISE_ICON, LOG + " verifySessionInfo"
+                    + ", verify(sessionInfo)=" + verify(sessionInfo)
+                    + ", reason=" + (sessionInfo == null ? null : sessionInfo.getInstallReason())
+                    + ", PackageManager.INSTALL_REASON_USER=" + PackageManager.INSTALL_REASON_USER
+                    + ", hasIcon=" + (sessionInfo != null && sessionInfo.getAppIcon() != null)
+                    + ", label is ! empty=" + labelNotEmpty
+                    + " +, app not installed="  + appNotInstalled);
+        }
         return verify(sessionInfo) != null
                 && sessionInfo.getInstallReason() == PackageManager.INSTALL_REASON_USER
                 && sessionInfo.getAppIcon() != null
diff --git a/src/com/android/launcher3/pm/InstallSessionTracker.java b/src/com/android/launcher3/pm/InstallSessionTracker.java
index e1b3c1a..75cf7a8 100644
--- a/src/com/android/launcher3/pm/InstallSessionTracker.java
+++ b/src/com/android/launcher3/pm/InstallSessionTracker.java
@@ -25,10 +25,12 @@
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.os.Build;
 import android.os.UserHandle;
+import android.util.Log;
 import android.util.SparseArray;
 
 import androidx.annotation.WorkerThread;
 
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.PackageUserKey;
 
 import java.lang.ref.WeakReference;
@@ -57,10 +59,19 @@
     public void onCreated(int sessionId) {
         InstallSessionHelper helper = mWeakHelper.get();
         Callback callback = mWeakCallback.get();
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.MISSING_PROMISE_ICON, "Session created sessionId=" + sessionId
+                    + ", callback=" + callback
+                    + ", helper=" + helper);
+        }
         if (callback == null || helper == null) {
             return;
         }
         SessionInfo sessionInfo = pushSessionDisplayToLauncher(sessionId, helper, callback);
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.MISSING_PROMISE_ICON, "Session created sessionId=" + sessionId
+                    + ", sessionInfo=" + sessionInfo);
+        }
         if (sessionInfo != null) {
             callback.onInstallSessionCreated(PackageInstallInfo.fromInstallingState(sessionInfo));
         }
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 7c6ad9f..4f40567 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -68,6 +68,7 @@
         }
     }
 
+    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 =
@@ -104,6 +105,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 =
@@ -126,4 +131,5 @@
     public static final String TASK_VIEW_ID_CRASH = "b/195430732";
     public static final String NO_DROP_TARGET = "b/195031154";
     public static final String NULL_INT_SET = "b/200572078";
+    public static final String MISSING_PROMISE_ICON = "b/202985412";
 }
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/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index a6c9c4d..4a55d2e 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -310,9 +310,10 @@
     }
 
     @Override
-    public Pair<Float, Float> setDwbLayoutParamsAndGetTranslations(int taskViewWidth,
+    public Pair<Float, Float> getDwbLayoutTranslations(int taskViewWidth,
             int taskViewHeight, StagedSplitBounds splitBounds, DeviceProfile deviceProfile,
             View[] thumbnailViews, int desiredTaskId, View banner) {
+        boolean isRtl = banner.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
         float translationX = 0;
         float translationY = 0;
         FrameLayout.LayoutParams bannerParams = (FrameLayout.LayoutParams) banner.getLayoutParams();
@@ -323,7 +324,7 @@
         FrameLayout.LayoutParams snapshotParams =
                 (FrameLayout.LayoutParams) thumbnailViews[0]
                         .getLayoutParams();
-        bannerParams.gravity = TOP | START;
+        bannerParams.gravity = TOP | (isRtl ? END : START);
         if (splitBounds == null) {
             // Single, fullscreen case
             bannerParams.width = taskViewHeight - snapshotParams.topMargin;
@@ -339,9 +340,11 @@
 
         // Set translations
         if (desiredTaskId == splitBounds.rightBottomTaskId) {
-            translationY = (snapshotParams.topMargin + taskViewHeight)
-                    * (splitBounds.leftTaskPercent) +
-                    (taskViewHeight * splitBounds.dividerWidthPercent);
+            float topLeftTaskPlusDividerPercent = splitBounds.appsStackedVertically
+                    ? (splitBounds.topTaskPercent + splitBounds.dividerHeightPercent)
+                    : (splitBounds.leftTaskPercent + splitBounds.dividerWidthPercent);
+            translationY = snapshotParams.topMargin
+                    + ((taskViewHeight - snapshotParams.topMargin) * topLeftTaskPlusDividerPercent);
         }
         if (desiredTaskId == splitBounds.leftTopTaskId) {
             translationY = snapshotParams.topMargin;
@@ -440,7 +443,9 @@
             StagedSplitBounds splitBoundsConfig, DeviceProfile dp) {
         int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
         int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
-        int dividerBar = splitBoundsConfig.visualDividerBounds.width();
+        int dividerBar = splitBoundsConfig.appsStackedVertically
+                ? splitBoundsConfig.visualDividerBounds.height()
+                : splitBoundsConfig.visualDividerBounds.width();
         int primarySnapshotHeight;
         int primarySnapshotWidth;
         int secondarySnapshotHeight;
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index 19c4639..923dcc6 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -191,7 +191,12 @@
      */
     PointF getAdditionalInsetForTaskMenu(float margin);
 
-    Pair<Float, Float> setDwbLayoutParamsAndGetTranslations(int taskViewWidth,
+    /**
+     * Calculates the position where a Digital Wellbeing Banner should be placed on its parent
+     * TaskView.
+     * @return A Pair of Floats representing the proper x and y translations.
+     */
+    Pair<Float, Float> getDwbLayoutTranslations(int taskViewWidth,
             int taskViewHeight, StagedSplitBounds splitBounds, DeviceProfile deviceProfile,
             View[] thumbnailViews, int desiredTaskId, View banner);
 
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index 01aea05..a308182 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -324,7 +324,7 @@
     }
 
     @Override
-    public Pair<Float, Float> setDwbLayoutParamsAndGetTranslations(int taskViewWidth,
+    public Pair<Float, Float> getDwbLayoutTranslations(int taskViewWidth,
             int taskViewHeight, StagedSplitBounds splitBounds, DeviceProfile deviceProfile,
             View[] thumbnailViews, int desiredTaskId, View banner) {
         float translationX = 0;
@@ -360,8 +360,11 @@
                 FrameLayout.LayoutParams snapshotParams =
                         (FrameLayout.LayoutParams) thumbnailViews[0]
                                 .getLayoutParams();
+                float bottomRightTaskPlusDividerPercent = splitBounds.appsStackedVertically
+                        ? (1f - splitBounds.topTaskPercent)
+                        : (1f - splitBounds.leftTaskPercent);
                 translationY = -((taskViewHeight - snapshotParams.topMargin)
-                        * (1f - splitBounds.topTaskPercent));
+                        * bottomRightTaskPlusDividerPercent);
             }
         }
         return new Pair<>(translationX, translationY);
diff --git a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
index 80a7229..6dc0c9a 100644
--- a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
@@ -106,21 +106,25 @@
         return new PointF(-margin, margin);
     }
 
+
+
     @Override
-    public Pair<Float, Float> setDwbLayoutParamsAndGetTranslations(int taskViewWidth,
+    public Pair<Float, Float> getDwbLayoutTranslations(int taskViewWidth,
             int taskViewHeight, StagedSplitBounds splitBounds, DeviceProfile deviceProfile,
             View[] thumbnailViews, int desiredTaskId, View banner) {
+        boolean isRtl = banner.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
         float translationX = 0;
         float translationY = 0;
         FrameLayout.LayoutParams bannerParams = (FrameLayout.LayoutParams) banner.getLayoutParams();
         banner.setPivotX(0);
         banner.setPivotY(0);
         banner.setRotation(getDegreesRotated());
+        translationX = taskViewWidth - banner.getHeight();
         FrameLayout.LayoutParams snapshotParams =
                 (FrameLayout.LayoutParams) thumbnailViews[0]
                         .getLayoutParams();
-        bannerParams.gravity = BOTTOM | END;
-        translationX = taskViewWidth - banner.getHeight();
+        bannerParams.gravity = BOTTOM | (isRtl ? END : START);
+
         if (splitBounds == null) {
             // Single, fullscreen case
             bannerParams.width = taskViewHeight - snapshotParams.topMargin;
@@ -130,19 +134,22 @@
 
         // Set correct width
         if (desiredTaskId == splitBounds.leftTopTaskId) {
-            bannerParams.width = thumbnailViews[1].getMeasuredHeight();
-        } else {
             bannerParams.width = thumbnailViews[0].getMeasuredHeight();
+        } else {
+            bannerParams.width = thumbnailViews[1].getMeasuredHeight();
         }
 
         // Set translations
         if (desiredTaskId == splitBounds.rightBottomTaskId) {
-            translationY = -(taskViewHeight - snapshotParams.topMargin)
-                    * (1f - splitBounds.leftTaskPercent)
-                    + banner.getHeight();
+            translationY = banner.getHeight();
         }
         if (desiredTaskId == splitBounds.leftTopTaskId) {
-            translationY = banner.getHeight();
+            float bottomRightTaskPlusDividerPercent = splitBounds.appsStackedVertically
+                    ? (1f - splitBounds.topTaskPercent)
+                    : (1f - splitBounds.leftTaskPercent);
+            translationY = banner.getHeight()
+                    - ((taskViewHeight - snapshotParams.topMargin)
+                    * bottomRightTaskPlusDividerPercent);
         }
         return new Pair<>(translationX, translationY);
     }
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..106bb92 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -560,6 +560,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 +579,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 +636,7 @@
         mLoadIconSignal = null;
         mEndRunnable = null;
         mFinalDrawableBounds.setEmpty();
+        mIsOpening = false;
         mPositionOut = null;
         mListenerView.setListener(null);
         mOriginalIcon = null;
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 4b4f1d9..2035da1 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;
 
@@ -368,8 +370,7 @@
             AllApps allApps = mLauncher.getWorkspace().switchToAllApps();
             allApps.freeze();
             try {
-                appIcon = allApps.getAppIcon(name);
-                appIcon.dragToWorkspace(false, false);
+                allApps.getAppIcon(name).dragToWorkspace(false, false);
             } finally {
                 allApps.unfreeze();
             }
@@ -431,7 +432,7 @@
     @PortraitLandscape
     public void testDeleteFromWorkspace() throws Exception {
         // test delete both built-in apps and user-installed app from workspace
-        for (String appName : new String[] {"Gmail", "Play Store", APP_NAME}) {
+        for (String appName : new String[]{"Gmail", "Play Store", APP_NAME}) {
             final AppIcon appIcon = createShortcutIfNotExist(appName);
             Workspace workspace = mLauncher.getWorkspace().deleteAppIcon(appIcon);
             assertNull(appName + " app was found after being deleted from workspace",
@@ -481,7 +482,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 AllApps 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 AppIcon 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/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..800322b 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -51,8 +51,7 @@
         // 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);
     }
 
@@ -211,7 +210,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/AppIcon.java b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
index 8fa9e12..50611d7 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
@@ -27,6 +27,7 @@
 
 import com.android.launcher3.testing.TestProtocol;
 
+import java.util.function.Supplier;
 import java.util.regex.Pattern;
 
 /**
@@ -34,8 +35,11 @@
  */
 public abstract class AppIcon extends Launchable implements FolderDragTarget {
 
+    private final String mAppName;
+
     AppIcon(LauncherInstrumentation launcher, UiObject2 icon) {
         super(launcher, icon);
+        mAppName = icon.getText();
     }
 
     static BySelector getAppIconSelector(String appName, LauncherInstrumentation launcher) {
@@ -138,4 +142,31 @@
             );
         }
     }
+
+    /**
+     * Drag an object to the given cell in workspace. The target cell must be empty.
+     *
+     * @param cellX zero based column number, starting from the left of the screen.
+     * @param cellY zero based row number, starting from the top of the screen.
+     */
+    public AppIcon dragToWorkspace(int cellX, int cellY) {
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+             LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                     String.format("want to drag the icon to cell(%d, %d)", cellX, cellY))
+        ) {
+            final Supplier<Point> dest = () -> Workspace.getCellCenter(mLauncher, cellX, cellY);
+            Workspace.dragIconToWorkspace(mLauncher, this, dest, true, getLongPressIndicator(),
+                    () -> addExpectedEventsForLongClick(), null);
+            try (LauncherInstrumentation.Closable ignore = mLauncher.addContextLayer("dragged")) {
+                WorkspaceAppIcon appIcon =
+                        (WorkspaceAppIcon) mLauncher.getWorkspace().getWorkspaceAppIcon(mAppName);
+                mLauncher.assertTrue(
+                        String.format(
+                                "The %s icon should be in the cell (%d, %d).", mAppName, cellX,
+                                cellY),
+                        appIcon.isInCell(cellX, cellY));
+                return appIcon;
+            }
+        }
+    }
 }
\ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index d84d723..4333b27 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -80,7 +80,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 +90,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 +129,7 @@
                         }
                         String upPointerToEnterOverviewActionName =
                                 "sending UP pointer to enter overview";
-                        mLauncher.runToState(this::sendUpPointerToEnterOverviewToLauncher,
+                        mLauncher.runToState(() -> sendUpPointerToEnterOverviewToLauncher(downTime),
                                 OVERVIEW_STATE_ORDINAL, upPointerToEnterOverviewActionName);
                     }
                 }
@@ -153,21 +155,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 +180,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,18 +194,18 @@
                 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);
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index c22b4da..7c377d5 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;
@@ -105,7 +106,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");
@@ -301,9 +302,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 +317,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);
@@ -1757,4 +1768,4 @@
         return ResourceUtils.getBoolByName(
                 "config_supportsRoundedCornersOnWindows", resources, false);
     }
-}
\ No newline at end of file
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 16987e9..1947da3 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -41,6 +41,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;
@@ -88,7 +89,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
@@ -180,8 +181,8 @@
      *
      * @param appIcon   - 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) {
         if (mHotseat.getVisibleBounds().height() > mHotseat.getVisibleBounds().width()) {
@@ -266,8 +267,9 @@
     /**
      * 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 launcher              the root TAPL instrumentation object of {@link
+     *                              LauncherInstrumentation} type.
+     * @param appIcon               to be uninstalled.
      * @param expectLongClickEvents the runnable to be executed to verify expected longclick event.
      * @return validated workspace after the existing appIcon being uninstalled.
      */
@@ -305,6 +307,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.
diff --git a/tests/tapl/com/android/launcher3/tapl/WorkspaceAppIcon.java b/tests/tapl/com/android/launcher3/tapl/WorkspaceAppIcon.java
index 5f4e469..0523a63 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;
@@ -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);
+    }
 }