Merge "Making Launcher TAPL check orientation only when the expected orientation is set by the test" into tm-qpr-dev
diff --git a/go/quickstep/res/values-my/strings.xml b/go/quickstep/res/values-my/strings.xml
index 0ca0e9c..cbb485a 100644
--- a/go/quickstep/res/values-my/strings.xml
+++ b/go/quickstep/res/values-my/strings.xml
@@ -5,7 +5,7 @@
     <string name="action_listen" msgid="2370304050784689486">"နားထောင်ရန်"</string>
     <string name="action_translate" msgid="8028378961867277746">"ဘာသာပြန်ရန်"</string>
     <string name="action_search" msgid="6269564710943755464">"Lens"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"ရပြီ"</string>
+    <string name="dialog_acknowledge" msgid="2804025517675853172">"နားလည်ပြီ"</string>
     <string name="dialog_cancel" msgid="6464336969134856366">"မလုပ်တော့"</string>
     <string name="dialog_settings" msgid="6564397136021186148">"ဆက်တင်များ"</string>
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"ဖန်သားပြင်ပေါ်ရှိ စာသားကို ဘာသာပြန်ပါ (သို့) နားထောင်ပါ"</string>
diff --git a/quickstep/res/values-am/strings.xml b/quickstep/res/values-am/strings.xml
index 6ea7d8a..1789c2b 100644
--- a/quickstep/res/values-am/strings.xml
+++ b/quickstep/res/values-am/strings.xml
@@ -20,7 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="recent_task_option_pin" msgid="7929860679018978258">"ሰካ"</string>
-    <string name="recent_task_option_freeform" msgid="48863056265284071">"ነጻ ቅጽ"</string>
+    <string name="recent_task_option_freeform" msgid="48863056265284071">"ነፃ ቅጽ"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"ምንም የቅርብ ጊዜ ንጥሎች የሉም"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"የመተግበሪያ አጠቃቀም ቅንብሮች"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"ሁሉንም አጽዳ"</string>
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
index 2100834..80bdb6f 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
@@ -38,6 +38,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.uioverrides.PredictedAppIcon;
 import com.android.launcher3.views.AbstractSlideInView;
@@ -192,7 +193,7 @@
             icon.setEnabled(false);
             icon.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
             icon.verifyHighRes();
-            CellLayout.LayoutParams lp = new CellLayout.LayoutParams(i, 0, 1, 1);
+            CellLayoutLayoutParams lp = new CellLayoutLayoutParams(i, 0, 1, 1);
             mSampleHotseat.addViewToCellLayout(icon, i, info.getViewId(), lp, true);
         }
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 5178968..b9b4fc3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -173,7 +173,7 @@
             }
         }
 
-        if (ENABLE_SHELL_TRANSITIONS
+        if (ENABLE_SHELL_TRANSITIONS && isResumed
                 && !mLauncher.getStateManager().getState().isTaskbarAlignedWithHotseat(mLauncher)) {
             // Launcher is resumed, but in a state where taskbar is still independent, so
             // ignore the state change.
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 339f3a9..af422cb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -23,7 +23,6 @@
 import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
 import static com.android.launcher3.taskbar.LauncherTaskbarUIController.SYSUI_SURFACE_PROGRESS_INDEX;
 import static com.android.launcher3.taskbar.TaskbarManager.isPhoneButtonNavMode;
-import static com.android.launcher3.taskbar.TaskbarManager.isPhoneMode;
 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y;
 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK;
 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME;
@@ -54,6 +53,7 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Color;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.graphics.Region.Op;
@@ -81,6 +81,9 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AlphaUpdateListener;
 import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton;
+import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory;
+import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.NavButtonLayoutter;
+import com.android.launcher3.util.DimensionUtils;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.views.BaseDragLayer;
@@ -181,6 +184,7 @@
     private final ViewTreeObserver.OnComputeInternalInsetsListener mSeparateWindowInsetsComputer =
             this::onComputeInsetsForSeparateWindow;
     private final RecentsHitboxExtender mHitboxExtender = new RecentsHitboxExtender();
+    private View mRecentsButton;
 
     public NavbarButtonsViewController(TaskbarActivityContext context, FrameLayout navButtonsView) {
         mContext = context;
@@ -203,11 +207,11 @@
         boolean isThreeButtonNav = mContext.isThreeButtonNav();
         DeviceProfile deviceProfile = mContext.getDeviceProfile();
         Resources resources = mContext.getResources();
-        mNavButtonsView.getLayoutParams().height = !isPhoneMode(deviceProfile) ?
-                mContext.isUserSetupComplete()
-                        ? deviceProfile.taskbarSize
-                        : resources.getDimensionPixelSize(R.dimen.taskbar_suw_frame)
-                : resources.getDimensionPixelSize(R.dimen.taskbar_size);
+        Point p = !mContext.isUserSetupComplete()
+                ? new Point(0, resources.getDimensionPixelSize(R.dimen.taskbar_suw_frame))
+                : DimensionUtils.getTaskbarPhoneDimensions(deviceProfile, resources,
+                        TaskbarManager.isPhoneMode(deviceProfile));
+        mNavButtonsView.getLayoutParams().height = p.y;
 
         mIsImeRenderingNavButtons =
                 InputMethodService.canImeRenderGesturalNavButtons() && mContext.imeDrawsImeNavBar();
@@ -268,81 +272,6 @@
                     mControllers.navButtonController);
             updateButtonLayoutSpacing();
             updateStateForFlag(FLAG_SMALL_SCREEN, isPhoneButtonNavMode(mContext));
-            if (isInSetup) {
-                handleSetupUi();
-
-                // Hide back button in SUW if keyboard is showing (IME draws its own back).
-                mPropertyHolders.add(new StatePropertyHolder(
-                        mBackButtonAlpha.getProperty(ALPHA_INDEX_SUW),
-                        flags -> (flags & FLAG_IME_VISIBLE) == 0));
-
-                // TODO(b/210906568) Dark intensity is currently not propagated during setup, so set
-                //  it based on dark theme for now.
-                int mode = resources.getConfiguration().uiMode
-                        & Configuration.UI_MODE_NIGHT_MASK;
-                boolean isDarkTheme = mode == Configuration.UI_MODE_NIGHT_YES;
-                mTaskbarNavButtonDarkIntensity.updateValue(isDarkTheme ? 0 : 1);
-            } else if (isInKidsMode) {
-                int iconSize = resources.getDimensionPixelSize(
-                        R.dimen.taskbar_icon_size_kids);
-                int buttonWidth = resources.getDimensionPixelSize(
-                        R.dimen.taskbar_nav_buttons_width_kids);
-                int buttonHeight = resources.getDimensionPixelSize(
-                        R.dimen.taskbar_nav_buttons_height_kids);
-                int buttonRadius = resources.getDimensionPixelSize(
-                        R.dimen.taskbar_nav_buttons_corner_radius_kids);
-                int paddingleft = (buttonWidth - iconSize) / 2;
-                int paddingRight = paddingleft;
-                int paddingTop = (buttonHeight - iconSize) / 2;
-                int paddingBottom = paddingTop;
-
-                // Update icons
-                ((ImageView) mBackButton).setImageDrawable(
-                        mBackButton.getContext().getDrawable(R.drawable.ic_sysbar_back_kids));
-                ((ImageView) mBackButton).setScaleType(ImageView.ScaleType.FIT_CENTER);
-                mBackButton.setPadding(paddingleft, paddingTop, paddingRight, paddingBottom);
-                ((ImageView) mHomeButton).setImageDrawable(
-                        mHomeButton.getContext().getDrawable(R.drawable.ic_sysbar_home_kids));
-                ((ImageView) mHomeButton).setScaleType(ImageView.ScaleType.FIT_CENTER);
-                mHomeButton.setPadding(paddingleft, paddingTop, paddingRight, paddingBottom);
-
-                // Home button layout
-                LinearLayout.LayoutParams homeLayoutparams = new LinearLayout.LayoutParams(
-                        buttonWidth,
-                        buttonHeight
-                );
-                int homeButtonLeftMargin = resources.getDimensionPixelSize(
-                        R.dimen.taskbar_home_button_left_margin_kids);
-                homeLayoutparams.setMargins(homeButtonLeftMargin, 0, 0, 0);
-                mHomeButton.setLayoutParams(homeLayoutparams);
-
-                // Back button layout
-                LinearLayout.LayoutParams backLayoutParams = new LinearLayout.LayoutParams(
-                        buttonWidth,
-                        buttonHeight
-                );
-                int backButtonLeftMargin = resources.getDimensionPixelSize(
-                        R.dimen.taskbar_back_button_left_margin_kids);
-                backLayoutParams.setMargins(backButtonLeftMargin, 0, 0, 0);
-                mBackButton.setLayoutParams(backLayoutParams);
-
-                // Button backgrounds
-                int whiteWith10PctAlpha = Color.argb(0.1f, 1, 1, 1);
-                PaintDrawable buttonBackground = new PaintDrawable(whiteWith10PctAlpha);
-                buttonBackground.setCornerRadius(buttonRadius);
-                mHomeButton.setBackground(buttonBackground);
-                mBackButton.setBackground(buttonBackground);
-
-                // Update alignment within taskbar
-                FrameLayout.LayoutParams navButtonsLayoutParams = (FrameLayout.LayoutParams)
-                        mNavButtonContainer.getLayoutParams();
-                navButtonsLayoutParams.setMarginStart(navButtonsLayoutParams.getMarginEnd() / 2);
-                navButtonsLayoutParams.setMarginEnd(navButtonsLayoutParams.getMarginStart());
-                navButtonsLayoutParams.gravity = Gravity.CENTER;
-                mNavButtonContainer.requestLayout();
-
-                mHomeButton.setOnLongClickListener(null);
-            }
 
             // Animate taskbar background when either..
             // notification shade expanded AND not on keyguard
@@ -445,20 +374,20 @@
                         (flags & FLAG_DISABLE_HOME) == 0));
 
         // Recents button
-        View recentsButton = addButton(R.drawable.ic_sysbar_recent, BUTTON_RECENTS,
+        mRecentsButton = addButton(R.drawable.ic_sysbar_recent, BUTTON_RECENTS,
                 navContainer, navButtonController, R.id.recent_apps);
-        mHitboxExtender.init(recentsButton, mNavButtonsView, mContext.getDeviceProfile(),
+        mHitboxExtender.init(mRecentsButton, mNavButtonsView, mContext.getDeviceProfile(),
                 () -> {
                     float[] recentsCoords = new float[2];
-                    getDescendantCoordRelativeToAncestor(recentsButton, mNavButtonsView,
+                    getDescendantCoordRelativeToAncestor(mRecentsButton, mNavButtonsView,
                             recentsCoords, false);
                     return recentsCoords;
                 }, new Handler());
-        recentsButton.setOnClickListener(v -> {
+        mRecentsButton.setOnClickListener(v -> {
             navButtonController.onButtonClick(BUTTON_RECENTS, v);
             mHitboxExtender.onRecentsButtonClicked();
         });
-        mPropertyHolders.add(new StatePropertyHolder(recentsButton,
+        mPropertyHolders.add(new StatePropertyHolder(mRecentsButton,
                 flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 && (flags & FLAG_DISABLE_RECENTS) == 0
                         && !mContext.isNavBarKidsModeActive()));
 
@@ -773,14 +702,22 @@
                 || !mContext.isUserSetupComplete()) {
             return;
         }
-
-        if (isPhoneButtonNavMode(mContext)) {
-            updatePhoneButtonSpacing();
-            return;
-        }
-
         DeviceProfile dp = mContext.getDeviceProfile();
         Resources res = mContext.getResources();
+        boolean isInSetup = !mContext.isUserSetupComplete();
+        // TODO(b/244231596) we're getting the incorrect kidsMode value in small-screen
+        boolean isInKidsMode = mContext.isNavBarKidsModeActive();
+
+        if (TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW) {
+            boolean isThreeButtonNav = mContext.isThreeButtonNav();
+
+            NavButtonLayoutter navButtonLayoutter =
+                    NavButtonLayoutFactory.Companion.getUiLayoutter(
+                            dp, mNavButtonsView, res, isInKidsMode, isInSetup, isThreeButtonNav,
+                            TaskbarManager.isPhoneMode(dp));
+            navButtonLayoutter.layoutButtons(dp, isContextualButtonShowing());
+            return;
+        }
 
         // Add spacing after the end of the last nav button
         FrameLayout.LayoutParams navButtonParams =
@@ -816,38 +753,84 @@
                 buttonLayoutParams.setMarginEnd(spaceInBetween / 2);
             }
         }
-    }
 
-    /** Uniformly spaces out the 3 button nav for smaller phone screens */
-    private void updatePhoneButtonSpacing() {
-        DeviceProfile dp = mContext.getDeviceProfile();
-        Resources res = mContext.getResources();
+        if (isInSetup) {
+            handleSetupUi();
 
-        // TODO: Polish pending, this is just to make it usable
-        FrameLayout.LayoutParams navContainerParams =
-                (FrameLayout.LayoutParams) mNavButtonContainer.getLayoutParams();
-        int endStartMargins = res.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size);
-        navContainerParams.gravity = Gravity.CENTER;
-        navContainerParams.setMarginEnd(endStartMargins);
-        navContainerParams.setMarginStart(endStartMargins);
-        mNavButtonContainer.setLayoutParams(navContainerParams);
+            // Hide back button in SUW if keyboard is showing (IME draws its own back).
+            mPropertyHolders.add(new StatePropertyHolder(
+                    mBackButtonAlpha.getProperty(ALPHA_INDEX_SUW),
+                    flags -> (flags & FLAG_IME_VISIBLE) == 0));
 
-        // Add the spaces in between the nav buttons
-        int spaceInBetween = res.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween_phone);
-        for (int i = 0; i < mNavButtonContainer.getChildCount(); i++) {
-            View navButton = mNavButtonContainer.getChildAt(i);
-            LinearLayout.LayoutParams buttonLayoutParams =
-                    (LinearLayout.LayoutParams) navButton.getLayoutParams();
-            buttonLayoutParams.weight = 1;
-            if (i == 0) {
-                buttonLayoutParams.setMarginEnd(spaceInBetween / 2);
-            } else if (i == mNavButtonContainer.getChildCount() - 1) {
-                buttonLayoutParams.setMarginStart(spaceInBetween / 2);
-            } else {
-                buttonLayoutParams.setMarginStart(spaceInBetween / 2);
-                buttonLayoutParams.setMarginEnd(spaceInBetween / 2);
-            }
+            // TODO(b/210906568) Dark intensity is currently not propagated during setup, so set
+            //  it based on dark theme for now.
+            int mode = res.getConfiguration().uiMode
+                    & Configuration.UI_MODE_NIGHT_MASK;
+            boolean isDarkTheme = mode == Configuration.UI_MODE_NIGHT_YES;
+            mTaskbarNavButtonDarkIntensity.updateValue(isDarkTheme ? 0 : 1);
+        } else if (isInKidsMode) {
+            int iconSize = res.getDimensionPixelSize(
+                    R.dimen.taskbar_icon_size_kids);
+            int buttonWidth = res.getDimensionPixelSize(
+                    R.dimen.taskbar_nav_buttons_width_kids);
+            int buttonHeight = res.getDimensionPixelSize(
+                    R.dimen.taskbar_nav_buttons_height_kids);
+            int buttonRadius = res.getDimensionPixelSize(
+                    R.dimen.taskbar_nav_buttons_corner_radius_kids);
+            int paddingleft = (buttonWidth - iconSize) / 2;
+            int paddingRight = paddingleft;
+            int paddingTop = (buttonHeight - iconSize) / 2;
+            int paddingBottom = paddingTop;
+
+            // Update icons
+            ((ImageView) mBackButton).setImageDrawable(
+                    mBackButton.getContext().getDrawable(R.drawable.ic_sysbar_back_kids));
+            ((ImageView) mBackButton).setScaleType(ImageView.ScaleType.FIT_CENTER);
+            mBackButton.setPadding(paddingleft, paddingTop, paddingRight, paddingBottom);
+            ((ImageView) mHomeButton).setImageDrawable(
+                    mHomeButton.getContext().getDrawable(R.drawable.ic_sysbar_home_kids));
+            ((ImageView) mHomeButton).setScaleType(ImageView.ScaleType.FIT_CENTER);
+            mHomeButton.setPadding(paddingleft, paddingTop, paddingRight, paddingBottom);
+
+            // Home button layout
+            LinearLayout.LayoutParams homeLayoutparams = new LinearLayout.LayoutParams(
+                    buttonWidth,
+                    buttonHeight
+            );
+            int homeButtonLeftMargin = res.getDimensionPixelSize(
+                    R.dimen.taskbar_home_button_left_margin_kids);
+            homeLayoutparams.setMargins(homeButtonLeftMargin, 0, 0, 0);
+            mHomeButton.setLayoutParams(homeLayoutparams);
+
+            // Back button layout
+            LinearLayout.LayoutParams backLayoutParams = new LinearLayout.LayoutParams(
+                    buttonWidth,
+                    buttonHeight
+            );
+            int backButtonLeftMargin = res.getDimensionPixelSize(
+                    R.dimen.taskbar_back_button_left_margin_kids);
+            backLayoutParams.setMargins(backButtonLeftMargin, 0, 0, 0);
+            mBackButton.setLayoutParams(backLayoutParams);
+
+            // Button backgrounds
+            int whiteWith10PctAlpha = Color.argb(0.1f, 1, 1, 1);
+            PaintDrawable buttonBackground = new PaintDrawable(whiteWith10PctAlpha);
+            buttonBackground.setCornerRadius(buttonRadius);
+            mHomeButton.setBackground(buttonBackground);
+            mBackButton.setBackground(buttonBackground);
+
+            // Update alignment within taskbar
+            FrameLayout.LayoutParams navButtonsLayoutParams = (FrameLayout.LayoutParams)
+                    mNavButtonContainer.getLayoutParams();
+            navButtonsLayoutParams.setMarginStart(
+                    navButtonsLayoutParams.getMarginEnd() / 2);
+            navButtonsLayoutParams.setMarginEnd(navButtonsLayoutParams.getMarginStart());
+            navButtonsLayoutParams.gravity = Gravity.CENTER;
+            mNavButtonContainer.requestLayout();
+
+            mHomeButton.setOnLongClickListener(null);
         }
+
     }
 
     public void onDestroy() {
@@ -859,6 +842,8 @@
 
         moveNavButtonsBackToTaskbarWindow();
         mNavButtonContainer.removeAllViews();
+        mEndContextualContainer.removeAllViews();
+        mStartContextualContainer.removeAllViews();
         mAllButtons.clear();
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
index 6b67b50..e23e27e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
@@ -101,8 +101,8 @@
                     resources.getDimensionPixelSize(R.dimen.taskbar_stashed_small_screen);
         } else {
             mStashedHandleView.getLayoutParams().height = deviceProfile.taskbarSize;
-            mStashedHandleWidth =
-                    resources.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_width);
+            mStashedHandleWidth = resources
+                    .getDimensionPixelSize(R.dimen.taskbar_stashed_handle_width);
         }
 
         mTaskbarStashedHandleAlpha.getProperty(ALPHA_INDEX_STASHED).setValue(
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index c69dc0d..0c488cb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -89,6 +89,7 @@
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.rotation.RotationButtonController;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.unfold.updates.RotationChangeProvider;
 import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
 
 import java.io.PrintWriter;
@@ -153,6 +154,8 @@
                 Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE), 0);
         mIsNavBarForceVisible = settingsCache.getValue(
                 Settings.Secure.getUriFor(Settings.Secure.NAV_BAR_KIDS_MODE), 0);
+        // TODO(b/244231596) For shared Taskbar window, update this value in init() instead so
+        //  to get correct value when recreating the taskbar
         mIsNavBarKidsMode = settingsCache.getValue(
                 Settings.Secure.getUriFor(Settings.Secure.NAV_BAR_KIDS_MODE), 0);
 
@@ -198,7 +201,9 @@
                 new TaskbarViewController(this, taskbarView),
                 new TaskbarScrimViewController(this, taskbarScrimView),
                 new TaskbarUnfoldAnimationController(this, unfoldTransitionProgressProvider,
-                        mWindowManager, WindowManagerGlobal.getWindowManagerService()),
+                    mWindowManager,
+                    new RotationChangeProvider(WindowManagerGlobal.getWindowManagerService(), this,
+                        getMainExecutor())),
                 new TaskbarKeyguardController(this),
                 new StashedHandleViewController(this, stashedHandleView),
                 new TaskbarStashController(this),
@@ -273,9 +278,13 @@
      * @param type The window type to pass to the created WindowManager.LayoutParams.
      */
     public WindowManager.LayoutParams createDefaultWindowLayoutParams(int type) {
+        DeviceProfile deviceProfile = getDeviceProfile();
+        // Taskbar is on the logical bottom of the screen
+        boolean isVerticalBarLayout = TaskbarManager.isPhoneMode(deviceProfile) &&
+                deviceProfile.isLandscape;
         WindowManager.LayoutParams windowLayoutParams = new WindowManager.LayoutParams(
-                MATCH_PARENT,
-                mLastRequestedNonFullscreenHeight,
+                isVerticalBarLayout ? mLastRequestedNonFullscreenHeight : MATCH_PARENT,
+                isVerticalBarLayout ? MATCH_PARENT : mLastRequestedNonFullscreenHeight,
                 type,
                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                         | WindowManager.LayoutParams.FLAG_SLIPPERY
@@ -283,7 +292,10 @@
                 PixelFormat.TRANSLUCENT);
         windowLayoutParams.setTitle(WINDOW_TITLE);
         windowLayoutParams.packageName = getPackageName();
-        windowLayoutParams.gravity = Gravity.BOTTOM;
+        windowLayoutParams.gravity = !isVerticalBarLayout ?
+                Gravity.BOTTOM :
+                Gravity.END; // TODO(b/230394142): seascape
+
         windowLayoutParams.setFitInsetsTypes(0);
         windowLayoutParams.receiveInsetsIgnoringZOrder = true;
         windowLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
@@ -800,7 +812,7 @@
         return mIsUserSetupComplete;
     }
 
-    protected boolean isNavBarKidsModeActive() {
+    public boolean isNavBarKidsModeActive() {
         return mIsNavBarKidsMode && isThreeButtonNav();
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
index 025fe7a..353f1e0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
@@ -16,11 +16,13 @@
 package com.android.launcher3.taskbar;
 
 import android.content.res.Resources;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.view.ViewTreeObserver;
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
+import com.android.launcher3.util.DimensionUtils;
 import com.android.launcher3.util.TouchController;
 import com.android.quickstep.AnimatedFloat;
 
@@ -177,9 +179,12 @@
             DeviceProfile deviceProfile = mActivity.getDeviceProfile();
             if (TaskbarManager.isPhoneMode(deviceProfile)) {
                 Resources resources = mActivity.getResources();
-                return mActivity.isThreeButtonNav() ?
-                        resources.getDimensionPixelSize(R.dimen.taskbar_size) :
-                        resources.getDimensionPixelSize(R.dimen.taskbar_stashed_size);
+                Point taskbarDimensions =
+                        DimensionUtils.getTaskbarPhoneDimensions(deviceProfile, resources,
+                                TaskbarManager.isPhoneMode(deviceProfile));
+                return taskbarDimensions.y == -1 ?
+                        deviceProfile.getDisplayInfo().currentSize.y :
+                        taskbarDimensions.y;
             } else {
                 return deviceProfile.taskbarSize;
             }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
index 079e8a1..bbbc1e6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
@@ -84,16 +84,16 @@
         val tappableHeight = controllers.taskbarStashController.tappableHeightToReportToApps
         for (provider in windowLayoutParams.providedInsets) {
             if (provider.type == ITYPE_EXTRA_NAVIGATION_BAR) {
-                provider.insetsSize = Insets.of(0, 0, 0, contentHeight)
+                provider.insetsSize = getInsetsByNavMode(contentHeight)
             } else if (provider.type == ITYPE_BOTTOM_TAPPABLE_ELEMENT
                       || provider.type == ITYPE_BOTTOM_MANDATORY_GESTURES) {
-                provider.insetsSize = Insets.of(0, 0, 0, tappableHeight)
+                provider.insetsSize = getInsetsByNavMode(tappableHeight)
             }
         }
 
-        val imeInsetsSize = Insets.of(0, 0, 0, taskbarHeightForIme)
+        val imeInsetsSize = getInsetsByNavMode(taskbarHeightForIme)
         // Use 0 insets for the VoiceInteractionWindow (assistant) when gesture nav is enabled.
-        val visInsetsSize = Insets.of(0, 0, 0, if (context.isGestureNav) 0 else tappableHeight)
+        val visInsetsSize = getInsetsByNavMode(if (context.isGestureNav) 0 else tappableHeight)
         val insetsSizeOverride = arrayOf(
             InsetsFrameProvider.InsetsSizeOverride(
                 TYPE_INPUT_METHOD,
@@ -110,6 +110,21 @@
     }
 
     /**
+     * @return [Insets] where the [bottomInset] is either used as a bottom inset or
+     *         right/left inset if using 3 button nav
+     */
+    private fun getInsetsByNavMode(bottomInset: Int) : Insets {
+        val devicePortrait = !context.deviceProfile.isLandscape
+        if (!TaskbarManager.isPhoneButtonNavMode(context) || devicePortrait) {
+            // Taskbar or portrait phone mode
+            return Insets.of(0, 0, 0, bottomInset)
+        }
+
+        // TODO(b/230394142): seascape
+        return Insets.of(0, 0, bottomInset, 0)
+    }
+
+    /**
      * Sets {@param providesInsetsTypes} as the inset types provided by {@param params}.
      * @param params The window layout params.
      * @param providesInsetsTypes The inset types we would like this layout params to provide.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 98a1b01..c5e1b8f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -242,6 +242,7 @@
      */
     protected boolean supportsManualStashing() {
         return supportsVisualStashing()
+                && isInApp()
                 && (!Utilities.IS_RUNNING_IN_TEST_HARNESS || mEnableManualStashingDuringTests);
     }
 
@@ -714,8 +715,16 @@
         applyState(skipAnim ? 0 : animDuration, skipAnim ? 0 : startDelay);
     }
 
+    /**
+     * We stash when IME or IME switcher is showing AND NOT
+     *  * in small screen AND
+     *  * 3 button nav AND
+     *  * landscape (or seascape)
+     */
     private boolean shouldStashForIme() {
-        return mIsImeShowing || mIsImeSwitcherShowing;
+        return (mIsImeShowing || mIsImeSwitcherShowing) &&
+                !(isPhoneMode() && mActivity.isThreeButtonNav()
+                        && mActivity.getDeviceProfile().isLandscape);
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java
index 64a4fa7..4c937a7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java
@@ -15,13 +15,13 @@
  */
 package com.android.launcher3.taskbar;
 
-import android.view.IWindowManager;
 import android.view.View;
 import android.view.WindowManager;
 
 import com.android.quickstep.util.LauncherViewsMoveFromCenterTranslationApplier;
 import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator;
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener;
+import com.android.systemui.unfold.updates.RotationChangeProvider;
 import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider;
 import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
 
@@ -41,16 +41,20 @@
 
     public TaskbarUnfoldAnimationController(BaseTaskbarContext context,
             ScopedUnfoldTransitionProgressProvider source,
-            WindowManager windowManager, IWindowManager iWindowManager) {
+            WindowManager windowManager,
+            RotationChangeProvider rotationChangeProvider) {
         mScopedUnfoldTransitionProgressProvider = source;
         mNaturalUnfoldTransitionProgressProvider =
-                new NaturalRotationUnfoldProgressProvider(context, iWindowManager, source);
+                new NaturalRotationUnfoldProgressProvider(context,
+                        rotationChangeProvider,
+                        source);
         mMoveFromCenterAnimator = new UnfoldMoveFromCenterAnimator(windowManager,
                 new LauncherViewsMoveFromCenterTranslationApplier());
     }
 
     /**
      * Initializes the controller
+     *
      * @param taskbarControllers references to all other taskbar controllers
      */
     public void init(TaskbarControllers taskbarControllers) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
new file mode 100644
index 0000000..68ea27a
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.taskbar.navbutton
+
+import android.content.res.Resources
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.LinearLayout
+import com.android.launcher3.R
+import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.NavButtonLayoutter
+
+/**
+ * Meant to be a simple container for data subclasses will need
+ *
+ * Assumes that the 3 navigation buttons (back/home/recents) have already been added to
+ * [navButtonContainer]
+ *
+ * @property navButtonContainer ViewGroup that holds the 3 navigation buttons.
+ * @property endContextualContainer ViewGroup that holds the end contextual button (ex, IME dismiss).
+ * @property startContextualContainer ViewGroup that holds the start contextual button (ex, A11y).
+ */
+abstract class AbstractNavButtonLayoutter(
+        val resources: Resources,
+        val navButtonContainer: LinearLayout,
+        protected val endContextualContainer: ViewGroup,
+        protected val startContextualContainer: ViewGroup
+) : NavButtonLayoutter {
+    protected val homeButton: ImageView = navButtonContainer
+            .findViewById(R.id.home)
+    protected val recentsButton: ImageView = navButtonContainer
+            .findViewById(R.id.recent_apps)
+    protected val backButton: ImageView = navButtonContainer
+            .findViewById(R.id.back)
+}
+
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
new file mode 100644
index 0000000..c67ab79
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
@@ -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.taskbar.navbutton
+
+import android.content.res.Resources
+import android.graphics.Color
+import android.graphics.drawable.PaintDrawable
+import android.view.Gravity
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import android.widget.ImageView
+import android.widget.LinearLayout
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.DIMEN_TASKBAR_BACK_BUTTON_LEFT_MARGIN_KIDS
+import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.DIMEN_TASKBAR_HOME_BUTTON_LEFT_MARGIN_KIDS
+import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.DIMEN_TASKBAR_ICON_SIZE_KIDS
+import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.DIMEN_TASKBAR_NAV_BUTTONS_CORNER_RADIUS_KIDS
+import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.DIMEN_TASKBAR_NAV_BUTTONS_HEIGHT_KIDS
+import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.DIMEN_TASKBAR_NAV_BUTTONS_WIDTH_KIDS
+import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.DRAWABLE_SYSBAR_BACK_KIDS
+import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.DRAWABLE_SYSBAR_HOME_KIDS
+
+class KidsNavLayoutter(
+        resources: Resources,
+        navBarContainer: LinearLayout,
+        endContextualContainer: ViewGroup,
+        startContextualContainer: ViewGroup
+) : AbstractNavButtonLayoutter(
+        resources,
+        navBarContainer,
+        endContextualContainer,
+        startContextualContainer
+) {
+
+    override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
+        val iconSize: Int = resources.getDimensionPixelSize(
+                DIMEN_TASKBAR_ICON_SIZE_KIDS)
+        val buttonWidth: Int = resources.getDimensionPixelSize(
+                DIMEN_TASKBAR_NAV_BUTTONS_WIDTH_KIDS)
+        val buttonHeight: Int = resources.getDimensionPixelSize(
+                DIMEN_TASKBAR_NAV_BUTTONS_HEIGHT_KIDS)
+        val buttonRadius: Int = resources.getDimensionPixelSize(
+                DIMEN_TASKBAR_NAV_BUTTONS_CORNER_RADIUS_KIDS)
+        val paddingLeft = (buttonWidth - iconSize) / 2
+        val paddingTop = (buttonHeight - iconSize) / 2
+
+        // Update icons
+        backButton.setImageDrawable(
+                backButton.context.getDrawable(DRAWABLE_SYSBAR_BACK_KIDS))
+        backButton.scaleType = ImageView.ScaleType.FIT_CENTER
+        backButton.setPadding(paddingLeft, paddingTop, paddingLeft, paddingTop)
+        homeButton.setImageDrawable(
+                homeButton.getContext().getDrawable(DRAWABLE_SYSBAR_HOME_KIDS))
+        homeButton.scaleType = ImageView.ScaleType.FIT_CENTER
+        homeButton.setPadding(paddingLeft, paddingTop, paddingLeft, paddingTop)
+
+        // Home button layout
+        val homeLayoutparams = LinearLayout.LayoutParams(
+                buttonWidth,
+                buttonHeight
+        )
+        val homeButtonLeftMargin: Int = resources.getDimensionPixelSize(
+                DIMEN_TASKBAR_HOME_BUTTON_LEFT_MARGIN_KIDS)
+        homeLayoutparams.setMargins(homeButtonLeftMargin, 0, 0, 0)
+        homeButton.layoutParams = homeLayoutparams
+
+        // Back button layout
+        val backLayoutParams = LinearLayout.LayoutParams(
+                buttonWidth,
+                buttonHeight
+        )
+        val backButtonLeftMargin: Int = resources.getDimensionPixelSize(
+                DIMEN_TASKBAR_BACK_BUTTON_LEFT_MARGIN_KIDS)
+        backLayoutParams.setMargins(backButtonLeftMargin, 0, 0, 0)
+        backButton.layoutParams = backLayoutParams
+
+        // Button backgrounds
+        val whiteWith10PctAlpha = Color.argb(0.1f, 1f, 1f, 1f)
+        val buttonBackground = PaintDrawable(whiteWith10PctAlpha)
+        buttonBackground.setCornerRadius(buttonRadius.toFloat())
+        homeButton.background = buttonBackground
+        backButton.background = buttonBackground
+
+        // Update alignment within taskbar
+        val navButtonsLayoutParams = navButtonContainer.layoutParams as FrameLayout.LayoutParams
+        navButtonsLayoutParams.apply {
+            marginStart = navButtonsLayoutParams.marginEnd / 2
+            marginEnd = navButtonsLayoutParams.marginStart
+            gravity = Gravity.CENTER
+        }
+        navButtonContainer.requestLayout()
+
+        homeButton.onLongClickListener = null
+    }
+}
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/LayoutResourceHelper.java b/quickstep/src/com/android/launcher3/taskbar/navbutton/LayoutResourceHelper.java
new file mode 100644
index 0000000..0d9b855
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/LayoutResourceHelper.java
@@ -0,0 +1,68 @@
+/*
+ * 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.taskbar.navbutton;
+
+import android.annotation.DimenRes;
+import android.annotation.DrawableRes;
+import android.annotation.IdRes;
+
+import com.android.launcher3.R;
+
+/**
+ * A class for retrieving resources in Kotlin.
+ *
+ * This class should be removed once the build system supports resources loading in Kotlin.
+ */
+public final class LayoutResourceHelper {
+
+    // --------------------------
+    // Kids Nav Layout
+    @DimenRes
+    public static final int DIMEN_TASKBAR_ICON_SIZE_KIDS = R.dimen.taskbar_icon_size_kids;
+    @DrawableRes
+    public static final int DRAWABLE_SYSBAR_BACK_KIDS = R.drawable.ic_sysbar_back_kids;
+    @DrawableRes
+    public static final int DRAWABLE_SYSBAR_HOME_KIDS = R.drawable.ic_sysbar_home_kids;
+    @DimenRes
+    public static final int DIMEN_TASKBAR_HOME_BUTTON_LEFT_MARGIN_KIDS =
+            R.dimen.taskbar_home_button_left_margin_kids;
+    @DimenRes
+    public static final int DIMEN_TASKBAR_BACK_BUTTON_LEFT_MARGIN_KIDS =
+            R.dimen.taskbar_back_button_left_margin_kids;
+    @DimenRes
+    public static final int DIMEN_TASKBAR_NAV_BUTTONS_WIDTH_KIDS =
+            R.dimen.taskbar_nav_buttons_width_kids;
+    @DimenRes
+    public static final int DIMEN_TASKBAR_NAV_BUTTONS_HEIGHT_KIDS =
+            R.dimen.taskbar_nav_buttons_height_kids;
+    @DimenRes
+    public static final int DIMEN_TASKBAR_NAV_BUTTONS_CORNER_RADIUS_KIDS =
+            R.dimen.taskbar_nav_buttons_corner_radius_kids;
+
+    // --------------------------
+    // Nav Layout Factory
+    @IdRes
+    public static final int ID_START_CONTEXTUAL_BUTTONS = R.id.start_contextual_buttons;
+    @IdRes
+    public static final int ID_END_CONTEXTUAL_BUTTONS = R.id.end_contextual_buttons;
+    @IdRes
+    public static final int ID_END_NAV_BUTTONS = R.id.end_nav_buttons;
+
+    private LayoutResourceHelper() {
+
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
new file mode 100644
index 0000000..db0a2d8
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
@@ -0,0 +1,105 @@
+/*
+ * 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.taskbar.navbutton
+
+import android.content.res.Resources
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.ID_END_CONTEXTUAL_BUTTONS
+import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.ID_END_NAV_BUTTONS
+import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.ID_START_CONTEXTUAL_BUTTONS
+import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.Companion
+import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.NavButtonLayoutter
+
+/**
+ * Select the correct layout for nav buttons
+ *
+ * Since layouts are done dynamically for the nav buttons on Taskbar, this
+ * class returns a corresponding [NavButtonLayoutter] via
+ * [Companion.getUiLayoutter]
+ * that can help position the buttons based on the current [DeviceProfile]
+ */
+class NavButtonLayoutFactory {
+    companion object {
+        /**
+         * Get the correct instance of [NavButtonLayoutter]
+         *
+         * No layouts supported for configurations where:
+         *  * taskbar isn't showing AND
+         *  * the device is not in [phoneMode]
+         * OR
+         *  * phone is showing
+         *  * device is using gesture navigation
+         *
+         * @param navButtonsView ViewGroup that contains start, end, nav button ViewGroups
+         * @param isKidsMode no-op when taskbar is hidden/not showing
+         * @param isInSetup no-op when taskbar is hidden/not showing
+         * @param phoneMode refers to the device using the taskbar window on phones
+         * @param isThreeButtonNav are no-ops when taskbar is present/showing
+         */
+        fun getUiLayoutter(deviceProfile: DeviceProfile,
+                           navButtonsView: FrameLayout,
+                           resources: Resources,
+                           isKidsMode: Boolean,
+                           isInSetup: Boolean,
+                           isThreeButtonNav: Boolean,
+                           phoneMode: Boolean):
+                NavButtonLayoutter {
+            val navButtonContainer =
+                    navButtonsView.findViewById<LinearLayout>(ID_END_NAV_BUTTONS)
+            val endContextualContainer =
+                    navButtonsView.findViewById<ViewGroup>(ID_END_CONTEXTUAL_BUTTONS)
+            val startContextualContainer =
+                    navButtonsView.findViewById<ViewGroup>(ID_START_CONTEXTUAL_BUTTONS)
+            val isPhoneNavMode = phoneMode && isThreeButtonNav
+            return when {
+                isPhoneNavMode -> {
+                    if (!deviceProfile.isLandscape) {
+                        PhonePortraitNavLayoutter(resources, navButtonContainer,
+                                endContextualContainer, startContextualContainer)
+                    } else {
+                        PhoneLandscapeNavLayoutter(resources, navButtonContainer,
+                                endContextualContainer, startContextualContainer)
+                    }
+                }
+                deviceProfile.isTaskbarPresent -> {
+                    return when {
+                        isInSetup -> {
+                            SetupNavLayoutter(resources, navButtonContainer, endContextualContainer,
+                                    startContextualContainer)
+                        }
+                        isKidsMode -> {
+                            KidsNavLayoutter(resources, navButtonContainer, endContextualContainer,
+                                    startContextualContainer)
+                        }
+                        else ->
+                            TaskbarNavLayoutter(resources, navButtonContainer, endContextualContainer,
+                                    startContextualContainer)
+                    }
+                }
+                else -> error("No layoutter found")
+            }
+        }
+    }
+
+    /** Lays out and provides access to the home, recents, and back buttons for various mischief  */
+    interface NavButtonLayoutter {
+        fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean)
+    }
+}
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
new file mode 100644
index 0000000..a89476e
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.taskbar.navbutton
+
+import android.content.res.Resources
+import android.view.Gravity
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import androidx.core.view.children
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.R
+import com.android.launcher3.taskbar.TaskbarManager
+import com.android.launcher3.util.DimensionUtils
+
+class PhoneLandscapeNavLayoutter(
+        resources: Resources,
+        navBarContainer: LinearLayout,
+        endContextualContainer: ViewGroup,
+        startContextualContainer: ViewGroup
+) : AbstractNavButtonLayoutter(
+        resources,
+        navBarContainer,
+        endContextualContainer,
+        startContextualContainer
+) {
+
+    override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
+        // TODO(b/230395757): Polish pending, this is just to make it usable
+        val navContainerParams = navButtonContainer.layoutParams as FrameLayout.LayoutParams
+        val endStartMargins =
+                resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size)
+        val taskbarDimensions = DimensionUtils.getTaskbarPhoneDimensions(dp, resources,
+                TaskbarManager.isPhoneMode(dp))
+        navButtonContainer.removeAllViews()
+        navButtonContainer.orientation = LinearLayout.VERTICAL
+
+        navContainerParams.apply {
+            width = taskbarDimensions.x
+            height = ViewGroup.LayoutParams.MATCH_PARENT
+            gravity = Gravity.CENTER
+            topMargin = endStartMargins
+            bottomMargin = endStartMargins
+            marginEnd = 0
+            marginStart = 0
+        }
+
+        // Swap recents and back button
+        navButtonContainer.addView(recentsButton)
+        navButtonContainer.addView(homeButton)
+        navButtonContainer.addView(backButton)
+
+        navButtonContainer.layoutParams = navContainerParams
+
+        // Add the spaces in between the nav buttons
+        val spaceInBetween: Int =
+                resources.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween_phone)
+        navButtonContainer.children.forEachIndexed { i, navButton ->
+            val buttonLayoutParams = navButton.layoutParams as LinearLayout.LayoutParams
+            buttonLayoutParams.weight = 1f
+            when (i) {
+                0 -> {
+                    buttonLayoutParams.bottomMargin = spaceInBetween / 2
+                }
+                navButtonContainer.childCount - 1 -> {
+                    buttonLayoutParams.topMargin = spaceInBetween / 2
+                }
+                else -> {
+                    buttonLayoutParams.bottomMargin = spaceInBetween / 2
+                    buttonLayoutParams.topMargin = spaceInBetween / 2
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
new file mode 100644
index 0000000..275f59f
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.taskbar.navbutton
+
+import android.content.res.Resources
+import android.view.Gravity
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.R
+import com.android.launcher3.taskbar.TaskbarManager
+import com.android.launcher3.util.DimensionUtils
+
+class PhonePortraitNavLayoutter(resources: Resources, navBarContainer: LinearLayout,
+                                endContextualContainer: ViewGroup,
+                                startContextualContainer: ViewGroup) :
+        AbstractNavButtonLayoutter(resources, navBarContainer, endContextualContainer,
+                startContextualContainer) {
+
+    override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
+        // TODO(b/230395757): Polish pending, this is just to make it usable
+        val navContainerParams = navButtonContainer.layoutParams as FrameLayout.LayoutParams
+        val taskbarDimensions = DimensionUtils.getTaskbarPhoneDimensions(dp, resources,
+                TaskbarManager.isPhoneMode(dp))
+        val endStartMargins = resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size)
+        navContainerParams.width = taskbarDimensions.x
+        navContainerParams.height = ViewGroup.LayoutParams.MATCH_PARENT
+        navContainerParams.gravity = Gravity.CENTER_VERTICAL
+
+        // Ensure order of buttons is correct
+        navButtonContainer.removeAllViews()
+        navButtonContainer.orientation = LinearLayout.HORIZONTAL
+        navContainerParams.topMargin = 0
+        navContainerParams.bottomMargin = 0
+        navContainerParams.marginEnd = endStartMargins
+        navContainerParams.marginStart = endStartMargins
+        // Swap recents and back button in case we were landscape prior to this
+        navButtonContainer.addView(backButton)
+        navButtonContainer.addView(homeButton)
+        navButtonContainer.addView(recentsButton)
+
+        navButtonContainer.layoutParams = navContainerParams
+
+        // Add the spaces in between the nav buttons
+        val spaceInBetween =
+                resources.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween_phone)
+        for (i in 0 until navButtonContainer.childCount) {
+            val navButton = navButtonContainer.getChildAt(i)
+            val buttonLayoutParams = navButton.layoutParams as LinearLayout.LayoutParams
+            buttonLayoutParams.weight = 1f
+            when (i) {
+                0 -> {
+                    // First button
+                    buttonLayoutParams.marginEnd = spaceInBetween / 2
+                }
+                navButtonContainer.childCount - 1 -> {
+                    // Last button
+                    buttonLayoutParams.marginStart = spaceInBetween / 2
+                }
+                else -> {
+                    // other buttons
+                    buttonLayoutParams.marginStart = spaceInBetween / 2
+                    buttonLayoutParams.marginEnd = spaceInBetween / 2
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
new file mode 100644
index 0000000..afe70d6
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.taskbar.navbutton
+
+import android.content.res.Resources
+import android.view.Gravity
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import com.android.launcher3.DeviceProfile
+
+class SetupNavLayoutter(
+        resources: Resources,
+        navButtonContainer: LinearLayout,
+        endContextualContainer: ViewGroup,
+        startContextualContainer: ViewGroup
+) : AbstractNavButtonLayoutter(
+        resources,
+        navButtonContainer,
+        endContextualContainer,
+        startContextualContainer
+) {
+
+    override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
+        // Since setup wizard only has back button enabled, it looks strange to be
+        // end-aligned, so start-align instead.
+        val navButtonsLayoutParams = navButtonContainer.layoutParams as FrameLayout.LayoutParams
+        navButtonsLayoutParams.apply {
+            marginStart = navButtonsLayoutParams.marginEnd
+            marginEnd = 0
+            gravity = Gravity.START
+        }
+        navButtonContainer.requestLayout()
+    }
+}
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
new file mode 100644
index 0000000..b2ca2af
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.taskbar.navbutton
+
+import android.content.res.Resources
+import android.view.Gravity
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.R
+
+/**
+ * Layoutter for showing 3 button navigation on large screen
+ */
+class TaskbarNavLayoutter(
+        resources: Resources,
+        navBarContainer: LinearLayout,
+        endContextualContainer: ViewGroup,
+        startContextualContainer: ViewGroup
+) : AbstractNavButtonLayoutter(
+        resources,
+        navBarContainer,
+        endContextualContainer,
+        startContextualContainer
+) {
+
+    override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
+        // Add spacing after the end of the last nav button
+        val navButtonParams = navButtonContainer.layoutParams as FrameLayout.LayoutParams
+        var navMarginEnd = resources.getDimension(dp.inv.inlineNavButtonsEndSpacing).toInt()
+        val contextualWidth = endContextualContainer.width
+        // If contextual buttons are showing, we check if the end margin is enough for the
+        // contextual button to be showing - if not, move the nav buttons over a smidge
+        if (isContextualButtonShowing && navMarginEnd < contextualWidth) {
+            // Additional spacing, eat up half of space between last icon and nav button
+            navMarginEnd += resources.getDimensionPixelSize(R.dimen.taskbar_hotseat_nav_spacing) / 2
+        }
+
+        navButtonParams.apply {
+            gravity = Gravity.END
+            width = FrameLayout.LayoutParams.WRAP_CONTENT
+            height = ViewGroup.LayoutParams.MATCH_PARENT
+            marginEnd = navMarginEnd
+        }
+        navButtonContainer.orientation = LinearLayout.HORIZONTAL
+        navButtonContainer.layoutParams = navButtonParams
+
+        // Add the spaces in between the nav buttons
+        val spaceInBetween = resources.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween)
+        for (i in 0 until navButtonContainer.childCount) {
+            val navButton = navButtonContainer.getChildAt(i)
+            val buttonLayoutParams = navButton.layoutParams as LinearLayout.LayoutParams
+            buttonLayoutParams.weight = 0f
+            when (i) {
+                0 -> {
+                    buttonLayoutParams.marginEnd = spaceInBetween / 2
+                }
+                navButtonContainer.childCount - 1 -> {
+                    buttonLayoutParams.marginStart = spaceInBetween / 2
+                }
+                else -> {
+                    buttonLayoutParams.marginStart = spaceInBetween / 2
+                    buttonLayoutParams.marginEnd = spaceInBetween / 2
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index 5f3a990..bf0f8f7 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -49,6 +49,7 @@
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.AnimatorListeners;
+import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.GraphicsUtils;
 import com.android.launcher3.icons.IconNormalizer;
@@ -271,7 +272,7 @@
         mIsPinned = true;
         applyFromWorkspaceItem(info);
         setOnLongClickListener(ItemLongClickListener.INSTANCE_WORKSPACE);
-        ((CellLayout.LayoutParams) getLayoutParams()).canReorder = true;
+        ((CellLayoutLayoutParams) getLayoutParams()).canReorder = true;
         invalidate();
     }
 
@@ -280,7 +281,7 @@
      */
     public void finishBinding(OnLongClickListener longClickListener) {
         setOnLongClickListener(longClickListener);
-        ((CellLayout.LayoutParams) getLayoutParams()).canReorder = false;
+        ((CellLayoutLayoutParams) getLayoutParams()).canReorder = false;
         setTextVisibility(false);
         verifyHighRes();
     }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 4ca27b1..ee9845b 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -66,6 +66,7 @@
 import android.view.Display;
 import android.view.HapticFeedbackConstants;
 import android.view.View;
+import android.view.WindowManagerGlobal;
 import android.window.SplashScreen;
 
 import androidx.annotation.Nullable;
@@ -139,12 +140,14 @@
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.unfold.UnfoldSharedComponent;
 import com.android.systemui.unfold.UnfoldTransitionFactory;
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
 import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig;
 import com.android.systemui.unfold.config.UnfoldTransitionConfig;
 import com.android.systemui.unfold.system.ActivityManagerActivityTypeProvider;
 import com.android.systemui.unfold.system.DeviceStateManagerFoldProvider;
+import com.android.systemui.unfold.updates.RotationChangeProvider;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -173,6 +176,7 @@
     // Will be updated when dragging from taskbar.
     private @Nullable DragOptions mNextWorkspaceDragOptions = null;
     private @Nullable UnfoldTransitionProgressProvider mUnfoldTransitionProgressProvider;
+    private @Nullable RotationChangeProvider mRotationChangeProvider;
     private @Nullable LauncherUnfoldAnimationController mLauncherUnfoldAnimationController;
     /**
      * If Launcher restarted while in the middle of an Overview split select, it needs this data to
@@ -664,8 +668,8 @@
     private void initUnfoldTransitionProgressProvider() {
         final UnfoldTransitionConfig config = new ResourceUnfoldTransitionConfig();
         if (config.isEnabled()) {
-            mUnfoldTransitionProgressProvider =
-                    UnfoldTransitionFactory.createUnfoldTransitionProgressProvider(
+            UnfoldSharedComponent unfoldComponent =
+                    UnfoldTransitionFactory.createUnfoldSharedComponent(
                             /* context= */ this,
                             config,
                             ProxyScreenStatusProvider.INSTANCE,
@@ -677,13 +681,21 @@
                             getMainThreadHandler(),
                             getMainExecutor(),
                             /* backgroundExecutor= */ THREAD_POOL_EXECUTOR,
-                            /* tracingTagPrefix= */ "launcher"
+                            /* tracingTagPrefix= */ "launcher",
+                            WindowManagerGlobal.getWindowManagerService()
                     );
 
+            mUnfoldTransitionProgressProvider = unfoldComponent.getUnfoldTransitionProvider()
+                    .orElseThrow(() -> new IllegalStateException(
+                            "Trying to create UnfoldTransitionProgressProvider when the "
+                                    + "transition is disabled"));
+
+            mRotationChangeProvider = unfoldComponent.getRotationChangeProvider();
             mLauncherUnfoldAnimationController = new LauncherUnfoldAnimationController(
-                    this,
+                    /* launcher= */ this,
                     getWindowManager(),
-                    mUnfoldTransitionProgressProvider
+                    mUnfoldTransitionProgressProvider,
+                    mRotationChangeProvider
             );
         }
     }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 3fe2e59..0e1120b 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -90,9 +90,9 @@
             // While animating into recents, update the visible task data as needed
             builder.addOnFrameCallback(() -> mRecentsView.loadVisibleTaskData(FLAG_UPDATE_ALL));
             mRecentsView.updateEmptyMessage();
-            // TODO(b/238461210): Remove logging once root cause of flake detected.
+            // TODO(b/246283207): Remove logging once root cause of flake detected.
             if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
-                Log.d("b/238461210", "RecentsView#setStateWithAnimationInternal getCurrentPage(): "
+                Log.d("b/246283207", "RecentsView#setStateWithAnimationInternal getCurrentPage(): "
                                 + mRecentsView.getCurrentPage()
                                 + ", getScrollForPage(getCurrentPage())): "
                                 + mRecentsView.getScrollForPage(mRecentsView.getCurrentPage()));
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index 37a28e5..a9ff0fb 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -190,7 +190,8 @@
                 getCardinality(info), // cardinality = 16;
                 info.getWidget().getSpanX(), // span_x = 17 [default = 1];
                 info.getWidget().getSpanY(), // span_y = 18 [default = 1];
-                getAttributes(info) /* attributes */
+                getAttributes(info) /* attributes = 19 [(log_mode) = MODE_BYTES] */,
+                false /* is_kids_mode = 20 */
         );
     }
 
diff --git a/quickstep/src/com/android/quickstep/util/BaseUnfoldMoveFromCenterAnimator.java b/quickstep/src/com/android/quickstep/util/BaseUnfoldMoveFromCenterAnimator.java
index 143042f..2a513ee 100644
--- a/quickstep/src/com/android/quickstep/util/BaseUnfoldMoveFromCenterAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/BaseUnfoldMoveFromCenterAnimator.java
@@ -16,12 +16,14 @@
 package com.android.quickstep.util;
 
 import android.annotation.CallSuper;
+import android.view.Surface.Rotation;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowManager;
 
 import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator;
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener;
+import com.android.systemui.unfold.updates.RotationChangeProvider;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -32,15 +34,20 @@
 public abstract class BaseUnfoldMoveFromCenterAnimator implements TransitionProgressListener {
 
     private final UnfoldMoveFromCenterAnimator mMoveFromCenterAnimation;
+    private final RotationChangeProvider mRotationChangeProvider;
 
     private final Map<ViewGroup, Boolean> mOriginalClipToPadding = new HashMap<>();
     private final Map<ViewGroup, Boolean> mOriginalClipChildren = new HashMap<>();
 
+    private final UnfoldMoveFromCenterRotationListener mRotationListener =
+            new UnfoldMoveFromCenterRotationListener();
     private boolean mAnimationInProgress = false;
 
-    public BaseUnfoldMoveFromCenterAnimator(WindowManager windowManager) {
+    public BaseUnfoldMoveFromCenterAnimator(WindowManager windowManager,
+            RotationChangeProvider rotationChangeProvider) {
         mMoveFromCenterAnimation = new UnfoldMoveFromCenterAnimator(windowManager,
                 new LauncherViewsMoveFromCenterTranslationApplier());
+        mRotationChangeProvider = rotationChangeProvider;
     }
 
     @CallSuper
@@ -50,6 +57,7 @@
         mMoveFromCenterAnimation.updateDisplayProperties();
         onPrepareViewsForAnimation();
         onTransitionProgress(0f);
+        mRotationChangeProvider.addCallback(mRotationListener);
     }
 
     @CallSuper
@@ -62,6 +70,7 @@
     @Override
     public void onTransitionFinished() {
         mAnimationInProgress = false;
+        mRotationChangeProvider.removeCallback(mRotationListener);
         mMoveFromCenterAnimation.onTransitionFinished();
         clearRegisteredViews();
     }
@@ -109,4 +118,14 @@
             view.setClipChildren(originalClipChildren);
         }
     }
+
+    private class UnfoldMoveFromCenterRotationListener implements
+            RotationChangeProvider.RotationListener {
+
+        @Override
+        public void onRotationChanged(@Rotation int newRotation) {
+            mMoveFromCenterAnimation.updateDisplayProperties(newRotation);
+            updateRegisteredViewsIfNeeded();
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java b/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
index 97be437..f12074b 100644
--- a/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
+++ b/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
@@ -24,7 +24,6 @@
 import android.util.FloatProperty;
 import android.util.MathUtils;
 import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
 
 import androidx.core.view.OneShotPreDrawListener;
 
@@ -34,6 +33,7 @@
 import com.android.launcher3.util.HorizontalInsettableView;
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener;
+import com.android.systemui.unfold.updates.RotationChangeProvider;
 import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider;
 import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
 
@@ -62,16 +62,17 @@
     public LauncherUnfoldAnimationController(
             Launcher launcher,
             WindowManager windowManager,
-            UnfoldTransitionProgressProvider unfoldTransitionProgressProvider) {
+            UnfoldTransitionProgressProvider unfoldTransitionProgressProvider,
+            RotationChangeProvider rotationChangeProvider) {
         mLauncher = launcher;
         mProgressProvider = new ScopedUnfoldTransitionProgressProvider(
                 unfoldTransitionProgressProvider);
         mUnfoldMoveFromCenterHotseatAnimator = new UnfoldMoveFromCenterHotseatAnimator(launcher,
-                windowManager);
+                windowManager, rotationChangeProvider);
         mUnfoldMoveFromCenterWorkspaceAnimator = new UnfoldMoveFromCenterWorkspaceAnimator(launcher,
-                windowManager);
+                windowManager, rotationChangeProvider);
         mNaturalOrientationProgressProvider = new NaturalRotationUnfoldProgressProvider(launcher,
-                WindowManagerGlobal.getWindowManagerService(), mProgressProvider);
+                rotationChangeProvider, mProgressProvider);
         mNaturalOrientationProgressProvider.init();
 
         // Animated in all orientations
diff --git a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index eec8582..ad54a70 100644
--- a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -46,6 +46,7 @@
 import com.android.launcher3.Workspace;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.anim.SpringAnimationBuilder;
+import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
@@ -122,8 +123,7 @@
             if (grid.isVerticalBarLayout()) {
                 for (int i = hotseatIcons.getChildCount() - 1; i >= 0; i--) {
                     View child = hotseatIcons.getChildAt(i);
-                    CellLayout.LayoutParams lp =
-                            ((CellLayout.LayoutParams) child.getLayoutParams());
+                    CellLayoutLayoutParams lp = ((CellLayoutLayoutParams) child.getLayoutParams());
                     addStaggeredAnimationForView(child, lp.cellY + 1, totalRows, duration);
                 }
             } else {
@@ -193,7 +193,7 @@
         // Set up springs on workspace items.
         for (int i = itemsContainer.getChildCount() - 1; i >= 0; i--) {
             View child = itemsContainer.getChildAt(i);
-            CellLayout.LayoutParams lp = ((CellLayout.LayoutParams) child.getLayoutParams());
+            CellLayoutLayoutParams lp = ((CellLayoutLayoutParams) child.getLayoutParams());
             addStaggeredAnimationForView(child, lp.cellY + lp.cellVSpan, totalRows, duration);
         }
 
diff --git a/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterHotseatAnimator.java b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterHotseatAnimator.java
index dc97dd6..01a997a 100644
--- a/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterHotseatAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterHotseatAnimator.java
@@ -21,6 +21,7 @@
 
 import com.android.launcher3.Hotseat;
 import com.android.launcher3.Launcher;
+import com.android.systemui.unfold.updates.RotationChangeProvider;
 
 /**
  * Animation that moves hotseat icons from center to the sides (final position)
@@ -29,8 +30,9 @@
 
     private final Launcher mLauncher;
 
-    public UnfoldMoveFromCenterHotseatAnimator(Launcher launcher, WindowManager windowManager) {
-        super(windowManager);
+    public UnfoldMoveFromCenterHotseatAnimator(Launcher launcher, WindowManager windowManager,
+            RotationChangeProvider rotationChangeProvider) {
+        super(windowManager, rotationChangeProvider);
         mLauncher = launcher;
     }
 
diff --git a/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java
index 354d157..95a4b8f 100644
--- a/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java
@@ -22,6 +22,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.ShortcutAndWidgetContainer;
 import com.android.launcher3.Workspace;
+import com.android.systemui.unfold.updates.RotationChangeProvider;
 
 /**
  * Animation that moves launcher icons and widgets from center to the sides (final position)
@@ -30,8 +31,9 @@
 
     private final Launcher mLauncher;
 
-    public UnfoldMoveFromCenterWorkspaceAnimator(Launcher launcher, WindowManager windowManager) {
-        super(windowManager);
+    public UnfoldMoveFromCenterWorkspaceAnimator(Launcher launcher, WindowManager windowManager,
+            RotationChangeProvider rotationChangeProvider) {
+        super(windowManager, rotationChangeProvider);
         mLauncher = launcher;
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index eeaf238..c8ef958 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -1511,21 +1511,6 @@
             addView(mClearAllButton);
         }
 
-        boolean settlingOnNewTask = mNextPage != INVALID_PAGE;
-        if (settlingOnNewTask) {
-            // Restore mCurrentPage but don't call setCurrentPage() as that clobbers the scroll.
-            mCurrentPage = previousCurrentPage;
-        } else {
-            // TODO(b/238461210): Remove logging once root cause of flake detected.
-            if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
-                Log.d("b/238461210", "RecentsView#applyLoadPlan() -> !settlingOnNewTask -> "
-                                + "previousCurrentPage: " + previousCurrentPage
-                                + ", getScrollForPage(previousCurrentPage): "
-                                + getScrollForPage(previousCurrentPage));
-            }
-            setCurrentPage(previousCurrentPage);
-        }
-
         // Keep same previous focused task
         TaskView newFocusedTaskView = getTaskViewByTaskId(focusedTaskId);
         // If the list changed, maybe the focused task doesn't exist anymore
@@ -1550,21 +1535,36 @@
         }
 
         int targetPage = -1;
-        if (!settlingOnNewTask) {
+        if (mNextPage != INVALID_PAGE) {
+            // Restore mCurrentPage but don't call setCurrentPage() as that clobbers the scroll.
+            mCurrentPage = previousCurrentPage;
+            if (currentTaskId != -1) {
+                currentTaskView = getTaskViewByTaskId(currentTaskId);
+                if (currentTaskView != null) {
+                    targetPage = indexOfChild(currentTaskView);
+                }
+            }
+        } else {
             // Set the current page to the running task, but not if settling on new task.
             if (runningTaskId != -1) {
                 targetPage = indexOfChild(newRunningTaskView);
             } else if (getTaskViewCount() > 0) {
                 targetPage = indexOfChild(requireTaskViewAt(0));
             }
-        } else if (currentTaskId != -1) {
-            currentTaskView = getTaskViewByTaskId(currentTaskId);
-            if (currentTaskView != null) {
-                targetPage = indexOfChild(currentTaskView);
-            }
         }
         if (targetPage != -1 && mCurrentPage != targetPage) {
-            setCurrentPage(targetPage);
+            int finalTargetPage = targetPage;
+            runOnPageScrollsInitialized(() -> {
+                // TODO(b/246283207): Remove logging once root cause of flake detected.
+                if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+                    Log.d("b/246283207", "RecentsView#applyLoadPlan() -> "
+                            + "previousCurrentPage: " + previousCurrentPage
+                            + ", targetPage: " + finalTargetPage
+                            + ", getScrollForPage(targetPage): "
+                            + getScrollForPage(finalTargetPage));
+                }
+                setCurrentPage(finalTargetPage);
+            });
         }
 
         if (mIgnoreResetTaskId != -1 &&
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
new file mode 100644
index 0000000..58f0949
--- /dev/null
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
@@ -0,0 +1,148 @@
+package com.android.launcher3.taskbar.navbutton
+
+import android.content.res.Resources
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import android.widget.ImageView
+import android.widget.LinearLayout
+import androidx.test.runner.AndroidJUnit4
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.taskbar.TaskbarManager
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import com.android.launcher3.R
+import org.junit.Assume.assumeTrue
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+import java.lang.IllegalStateException
+
+@RunWith(AndroidJUnit4::class)
+class NavButtonLayoutFactoryTest {
+
+    @Mock
+    lateinit var mockDeviceProfile: DeviceProfile
+    @Mock
+    lateinit var mockParentButtonContainer: FrameLayout
+    @Mock
+    lateinit var mockNavLayout: LinearLayout
+    @Mock
+    lateinit var mockStartContextualLayout: ViewGroup
+    @Mock
+    lateinit var mockEndContextualLayout: ViewGroup
+    @Mock
+    lateinit var mockResources: Resources
+    @Mock
+    lateinit var mockBackButton: ImageView
+    @Mock
+    lateinit var mockRecentsButton: ImageView
+    @Mock
+    lateinit var mockHomeButton: ImageView
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        // Init end nav buttons
+        whenever(mockNavLayout.childCount).thenReturn(3)
+        whenever(mockNavLayout.findViewById<View>(R.id.back)).thenReturn(mockBackButton)
+        whenever(mockNavLayout.findViewById<View>(R.id.home)).thenReturn(mockHomeButton)
+        whenever(mockNavLayout.findViewById<View>(R.id.recent_apps)).thenReturn(mockRecentsButton)
+
+        // Init top level layout
+        whenever(mockParentButtonContainer.findViewById<LinearLayout>(R.id.end_nav_buttons))
+                .thenReturn(mockNavLayout)
+        whenever(mockParentButtonContainer.findViewById<ViewGroup>(R.id.end_contextual_buttons))
+                .thenReturn(mockEndContextualLayout)
+        whenever(mockParentButtonContainer.findViewById<ViewGroup>(R.id.start_contextual_buttons))
+                .thenReturn(mockStartContextualLayout)
+    }
+
+    @Test
+    fun getKidsLayoutter() {
+        assumeTrue(TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW)
+        mockDeviceProfile.isTaskbarPresent = true
+        val layoutter: NavButtonLayoutFactory.NavButtonLayoutter =
+                getLayoutter(isKidsMode = true, isInSetup = false, isThreeButtonNav = false,
+                        phoneMode = false)
+        assert(layoutter is KidsNavLayoutter)
+    }
+
+    @Test
+    fun getSetupLayoutter() {
+        assumeTrue(TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW)
+        mockDeviceProfile.isTaskbarPresent = true
+        val layoutter: NavButtonLayoutFactory.NavButtonLayoutter =
+                getLayoutter(isKidsMode = false, isInSetup = true, isThreeButtonNav = false,
+                        phoneMode = false)
+        assert(layoutter is SetupNavLayoutter)
+    }
+
+    @Test
+    fun getTaskbarNavLayoutter() {
+        assumeTrue(TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW)
+        mockDeviceProfile.isTaskbarPresent = true
+        val layoutter: NavButtonLayoutFactory.NavButtonLayoutter =
+                getLayoutter(isKidsMode = false, isInSetup = false, isThreeButtonNav = false,
+                        phoneMode = false)
+        assert(layoutter is TaskbarNavLayoutter)
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun noValidLayoutForLargeScreenTaskbarNotPresent() {
+        assumeTrue(TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW)
+        mockDeviceProfile.isTaskbarPresent = false
+        getLayoutter(isKidsMode = false, isInSetup = false, isThreeButtonNav = false,
+                        phoneMode = false)
+    }
+
+    @Test
+    fun getTaskbarPortraitLayoutter() {
+        assumeTrue(TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW)
+        mockDeviceProfile.isTaskbarPresent = false
+        val layoutter: NavButtonLayoutFactory.NavButtonLayoutter =
+                getLayoutter(isKidsMode = false, isInSetup = false, isThreeButtonNav = true,
+                        phoneMode = true)
+        assert(layoutter is PhonePortraitNavLayoutter)
+    }
+
+    @Test
+    fun getTaskbarLandscapeLayoutter() {
+        assumeTrue(TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW)
+        mockDeviceProfile.isTaskbarPresent = false
+        setDeviceProfileLandscape()
+        val layoutter: NavButtonLayoutFactory.NavButtonLayoutter =
+                getLayoutter(isKidsMode = false, isInSetup = false, isThreeButtonNav = true,
+                        phoneMode = true)
+        assert(layoutter is PhoneLandscapeNavLayoutter)
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun noValidLayoutForPhoneGestureNav() {
+        assumeTrue(TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW)
+        mockDeviceProfile.isTaskbarPresent = false
+        getLayoutter(isKidsMode = false, isInSetup = false, isThreeButtonNav = false,
+                phoneMode = true)
+    }
+
+    private fun setDeviceProfileLandscape() {
+        // Use reflection to modify landscape field
+        val landscapeField = mockDeviceProfile.javaClass.getDeclaredField("isLandscape")
+        landscapeField.isAccessible = true
+        landscapeField.set(mockDeviceProfile, true)
+    }
+
+    private fun getLayoutter(isKidsMode: Boolean, isInSetup: Boolean,
+                             isThreeButtonNav: Boolean, phoneMode: Boolean):
+            NavButtonLayoutFactory.NavButtonLayoutter {
+        return NavButtonLayoutFactory.getUiLayoutter(
+                deviceProfile = mockDeviceProfile,
+                navButtonsView = mockParentButtonContainer,
+                resources = mockResources,
+                isKidsMode = isKidsMode, isInSetup = isInSetup,
+                isThreeButtonNav = isThreeButtonNav, phoneMode = phoneMode
+        )
+    }
+}
\ No newline at end of file
diff --git a/res/layout/user_folder_icon_normalized.xml b/res/layout/user_folder_icon_normalized.xml
index 4dee6e7..39f93d9 100644
--- a/res/layout/user_folder_icon_normalized.xml
+++ b/res/layout/user_folder_icon_normalized.xml
@@ -34,8 +34,7 @@
         android:clipChildren="false"
         android:orientation="horizontal"
         android:paddingLeft="12dp"
-        android:paddingRight="12dp"
-        android:alpha="0">
+        android:paddingRight="12dp">
 
         <com.android.launcher3.folder.FolderNameEditText
             android:id="@+id/folder_name"
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index 0a4943e..67998d5 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -21,9 +21,9 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="649227358658669779">"Launcher3"</string>
     <string name="work_folder_name" msgid="3753320833950115786">"কৰ্মস্থান"</string>
-    <string name="activity_not_found" msgid="8071924732094499514">"এপটো ইনষ্টল কৰা নহ\'ল।"</string>
-    <string name="activity_not_available" msgid="7456344436509528827">"এপটো নাই"</string>
-    <string name="safemode_shortcut_error" msgid="9160126848219158407">"ডাউনল’ড কৰা এপটোক সুৰক্ষিত ম\'ডত অক্ষম কৰা হ’ল"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"এপ্‌টো ইনষ্টল কৰা নহ\'ল।"</string>
+    <string name="activity_not_available" msgid="7456344436509528827">"এপ্‌টো নাই"</string>
+    <string name="safemode_shortcut_error" msgid="9160126848219158407">"ডাউনল’ড কৰা এপ্‌টোক সুৰক্ষিত ম\'ডত অক্ষম কৰা হ’ল"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"ৱিজেটবোৰক সুৰক্ষিত ম\'ডত অক্ষম কৰা হ’ল"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"শ্বৰ্টকাট নাই"</string>
     <string name="home_screen" msgid="5629429142036709174">"গৃহ স্ক্ৰীন"</string>
@@ -119,8 +119,8 @@
     <string name="package_state_unknown" msgid="7592128424511031410">"অজ্ঞাত"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"আঁতৰাওক"</string>
     <string name="abandoned_search" msgid="891119232568284442">"সন্ধান কৰক"</string>
-    <string name="abandoned_promises_title" msgid="7096178467971716750">"এই এপটো ইনষ্টল কৰা হোৱা নাই"</string>
-    <string name="abandoned_promise_explanation" msgid="3990027586878167529">"এই আইকনৰ এপটো ইনষ্টল কৰা হোৱা নাই। আপুনি এইটো আঁতৰাব পাৰে অথবা এপটো বিচাৰি মেনুৱেলভাৱে ইনষ্টল কৰিব পাৰে।"</string>
+    <string name="abandoned_promises_title" msgid="7096178467971716750">"এই এপ্‌টো ইনষ্টল কৰা হোৱা নাই"</string>
+    <string name="abandoned_promise_explanation" msgid="3990027586878167529">"এই আইকনৰ এপ্‌টো ইনষ্টল কৰা হোৱা নাই। আপুনি এইটো আঁতৰাব পাৰে অথবা এপ্‌টো বিচাৰি মেনুৱেলভাৱে ইনষ্টল কৰিব পাৰে।"</string>
     <string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> ইনষ্টল কৰি থকা হৈছে, <xliff:g id="PROGRESS">%2$s</xliff:g> সম্পূৰ্ণ হৈছে"</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> ডাউনল’ড কৰি থকা হৈছে, <xliff:g id="PROGRESS">%2$s</xliff:g> সম্পূৰ্ণ হ’ল"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ইনষ্টল হোৱালৈ অপেক্ষা কৰি থকা হৈছে"</string>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index 3cec97d..69157bc 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -54,7 +54,7 @@
     <string name="widget_education_header" msgid="4874760613775913787">"အသုံးဝင်သော အချက်အလက်များကို အလွယ်တကူ ရယူလိုက်ပါ"</string>
     <string name="widget_education_content" msgid="1731667670753497052">"အက်ပ်မဖွင့်ဘဲ အချက်အလက်များရယူရန် ပင်မစာမျက်နှာတွင် ဝိဂျက်များ ထည့်နိုင်သည်"</string>
     <string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"ဝိဂျက် ဆက်တင်များကို ပြောင်းရန် တို့ပါ"</string>
-    <string name="widget_education_close_button" msgid="8676165703104836580">"ရပြီ"</string>
+    <string name="widget_education_close_button" msgid="8676165703104836580">"နားလည်ပြီ"</string>
     <string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"ဝိဂျက် ဆက်တင်များကို ပြောင်းပါ"</string>
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"ရှာဖွေမှု အက်ပ်များ"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"အက်ပ်များကို ဖွင့်နေသည်…"</string>
@@ -166,7 +166,7 @@
     <string name="work_apps_paused_body" msgid="261634750995824906">"သင်၏ အလုပ်သုံးအက်ပ်များက အကြောင်းကြားချက်များ ပို့ခြင်း၊ သင့်ဘက်ထရီ သုံးခြင်း (သို့) သင့်တည်နေရာ သုံးခြင်းတို့ မပြုလုပ်နိုင်ပါ"</string>
     <string name="work_apps_paused_content_description" msgid="5149623040804051095">"အလုပ်သုံးအက်ပ်များ ပိတ်ထားသည်။ သင်၏ အလုပ်သုံးအက်ပ်များက အကြောင်းကြားချက်များ ပို့ခြင်း၊ သင့်ဘက်ထရီ သုံးခြင်း (သို့) သင့်တည်နေရာ သုံးခြင်းတို့ မပြုလုပ်နိုင်ပါ"</string>
     <string name="work_apps_paused_edu_banner" msgid="8872412121608402058">"အလုပ်သုံးအက်ပ်များကို တံဆိပ်တပ်ထားပြီး သင်၏ IT စီမံခန့်ခွဲသူက မြင်နိုင်ပါသည်"</string>
-    <string name="work_apps_paused_edu_accept" msgid="6377476824357318532">"ရပြီ"</string>
+    <string name="work_apps_paused_edu_accept" msgid="6377476824357318532">"နားလည်ပြီ"</string>
     <string name="work_apps_pause_btn_text" msgid="4669288269140620646">"အလုပ်သုံးအက်ပ်များကို ခဏရပ်ရန်"</string>
     <string name="work_apps_enable_btn_text" msgid="1156432622148413741">"အလုပ်သုံးအက်ပ်များ ဖွင့်ရန်"</string>
     <string name="developer_options_filter_hint" msgid="5896817443635989056">"စစ်ထုတ်ရန်"</string>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 47584e2..a9ba07d 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -368,6 +368,7 @@
     <dimen name="taskbar_hotseat_nav_spacing">0dp</dimen>
     <dimen name="taskbar_button_margin_default">0dp</dimen>
     <dimen name="taskbar_button_space_inbetween">0dp</dimen>
+    <dimen name="taskbar_button_space_inbetween_phone">0dp</dimen>
     <dimen name="taskbar_button_margin_5_5">0dp</dimen>
     <dimen name="taskbar_button_margin_6_5">0dp</dimen>
     <dimen name="taskbar_button_margin_4_5">0dp</dimen>
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index fbb0a57..555fbb4 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -32,6 +32,7 @@
 import androidx.annotation.Px;
 
 import com.android.launcher3.accessibility.DragViewStateAnnouncer;
+import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
 import com.android.launcher3.logging.InstanceId;
@@ -265,7 +266,7 @@
             }
         }
 
-        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mWidgetView.getLayoutParams();
+        CellLayoutLayoutParams lp = (CellLayoutLayoutParams) mWidgetView.getLayoutParams();
         ItemInfo widgetInfo = (ItemInfo) mWidgetView.getTag();
         lp.cellX = lp.tmpCellX = widgetInfo.cellX;
         lp.cellY = lp.tmpCellY = widgetInfo.cellY;
@@ -405,7 +406,7 @@
      */
     private void resizeWidgetIfNeeded(boolean onDismiss) {
         ViewGroup.LayoutParams wlp = mWidgetView.getLayoutParams();
-        if (!(wlp instanceof CellLayout.LayoutParams)) {
+        if (!(wlp instanceof CellLayoutLayoutParams)) {
             return;
         }
         DeviceProfile dp = mLauncher.getDeviceProfile();
@@ -420,7 +421,7 @@
         mDirectionVector[0] = 0;
         mDirectionVector[1] = 0;
 
-        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) wlp;
+        CellLayoutLayoutParams lp = (CellLayoutLayoutParams) wlp;
 
         int spanX = lp.cellHSpan;
         int spanY = lp.cellVSpan;
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 9f3e1fa..c1df403 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -61,6 +61,7 @@
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.folder.PreviewBackground;
@@ -130,7 +131,7 @@
 
     // These arrays are used to implement the drag visualization on x-large screens.
     // They are used as circular arrays, indexed by mDragOutlineCurrent.
-    @Thunk final CellLayout.LayoutParams[] mDragOutlines = new CellLayout.LayoutParams[4];
+    @Thunk final CellLayoutLayoutParams[] mDragOutlines = new CellLayoutLayoutParams[4];
     @Thunk final float[] mDragOutlineAlphas = new float[mDragOutlines.length];
     private final InterruptibleInOutAnimator[] mDragOutlineAnims =
             new InterruptibleInOutAnimator[mDragOutlines.length];
@@ -139,7 +140,7 @@
     private int mDragOutlineCurrent = 0;
     private final Paint mDragOutlinePaint = new Paint();
 
-    @Thunk final ArrayMap<LayoutParams, Animator> mReorderAnimators = new ArrayMap<>();
+    @Thunk final ArrayMap<CellLayoutLayoutParams, Animator> mReorderAnimators = new ArrayMap<>();
     @Thunk final ArrayMap<Reorderable, ReorderPreviewAnimation> mShakeAnimators = new ArrayMap<>();
 
     private boolean mItemPlacementDirty = false;
@@ -269,7 +270,7 @@
         mDragCell[0] = mDragCell[1] = -1;
         mDragCellSpan[0] = mDragCellSpan[1] = -1;
         for (int i = 0; i < mDragOutlines.length; i++) {
-            mDragOutlines[i] = new CellLayout.LayoutParams(0, 0, 0, 0);
+            mDragOutlines[i] = new CellLayoutLayoutParams(0, 0, 0, 0);
         }
         mDragOutlinePaint.setColor(Themes.getAttrColor(context, R.attr.workspaceTextColor));
 
@@ -737,9 +738,19 @@
         return mContainerType == WORKSPACE;
     }
 
-    public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
-            boolean markCells) {
-        final LayoutParams lp = params;
+    /**
+     * Adds the given view to the CellLayout
+     *
+     * @param child view to add.
+     * @param index index of the CellLayout children where to add the view.
+     * @param childId id of the view.
+     * @param params represent the logic of the view on the CellLayout.
+     * @param markCells if the occupied cells should be marked or not
+     * @return if adding the view was successful
+     */
+    public boolean addViewToCellLayout(View child, int index, int childId,
+            CellLayoutLayoutParams params, boolean markCells) {
+        final CellLayoutLayoutParams lp = params;
 
         // Hotseat icons - remove text
         if (child instanceof BubbleTextView) {
@@ -1046,7 +1057,7 @@
         ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
 
         if (clc.indexOfChild(child) != -1 && (child instanceof Reorderable)) {
-            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            final CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
             final ItemInfo info = (ItemInfo) child.getTag();
             final Reorderable item = (Reorderable) child;
 
@@ -1153,7 +1164,7 @@
             mDragOutlineAnims[oldIndex].animateOut();
             mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
 
-            LayoutParams cell = mDragOutlines[mDragOutlineCurrent];
+            CellLayoutLayoutParams cell = mDragOutlines[mDragOutlineCurrent];
             cell.cellX = cellX;
             cell.cellY = cellY;
             cell.cellHSpan = spanX;
@@ -1708,7 +1719,7 @@
                 // cluster.
                 if (!cluster.views.contains(v) && v != dragView) {
                     if (cluster.isViewTouchingEdge(v, whichEdge)) {
-                        LayoutParams lp = (LayoutParams) v.getLayoutParams();
+                        CellLayoutLayoutParams lp = (CellLayoutLayoutParams) v.getLayoutParams();
                         if (!lp.canReorder) {
                             // The push solution includes the all apps button, this is not viable.
                             fail = true;
@@ -1919,7 +1930,7 @@
         for (View child: solution.map.keySet()) {
             if (child == ignoreView) continue;
             CellAndSpan c = solution.map.get(child);
-            LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
             r1.set(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY);
             if (Rect.intersects(r0, r1)) {
                 if (!lp.canReorder) {
@@ -2016,7 +2027,7 @@
         int childCount = mShortcutsAndWidgets.getChildCount();
         for (int i = 0; i < childCount; i++) {
             View child = mShortcutsAndWidgets.getChildAt(i);
-            LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
             CellAndSpan c;
             if (temp) {
                 c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
@@ -2034,7 +2045,7 @@
         for (int i = 0; i < childCount; i++) {
             View child = mShortcutsAndWidgets.getChildAt(i);
             if (child == dragView) continue;
-            LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
             CellAndSpan c = solution.map.get(child);
             if (c != null) {
                 lp.tmpCellX = c.cellX;
@@ -2082,7 +2093,7 @@
                     != null && !solution.intersectingViews.contains(child);
 
 
-            LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
             if (c != null && !skip && (child instanceof Reorderable)) {
                 ReorderPreviewAnimation rha = new ReorderPreviewAnimation((Reorderable) child,
                         mode, lp.cellX, lp.cellY, c.cellX, c.cellY, c.spanX, c.spanY);
@@ -2273,7 +2284,7 @@
         int childCount = mShortcutsAndWidgets.getChildCount();
         for (int i = 0; i < childCount; i++) {
             View child = mShortcutsAndWidgets.getChildAt(i);
-            LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
             ItemInfo info = (ItemInfo) child.getTag();
             // We do a null check here because the item info can be null in the case of the
             // AllApps button in the hotseat.
@@ -2298,7 +2309,8 @@
     private void setUseTempCoords(boolean useTempCoords) {
         int childCount = mShortcutsAndWidgets.getChildCount();
         for (int i = 0; i < childCount; i++) {
-            LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
+            CellLayoutLayoutParams lp = (CellLayoutLayoutParams) mShortcutsAndWidgets.getChildAt(
+                    i).getLayoutParams();
             lp.useTmpCoords = useTempCoords;
         }
     }
@@ -2383,7 +2395,8 @@
         for (int i = 0; i < count; i++) {
             View child = mShortcutsAndWidgets.getChildAt(i);
             if (child == dragView) continue;
-            LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            CellLayoutLayoutParams
+                    lp = (CellLayoutLayoutParams) child.getLayoutParams();
             r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
             if (Rect.intersects(r0, r1)) {
                 mIntersectingViews.add(child);
@@ -2408,7 +2421,8 @@
             final int count = mShortcutsAndWidgets.getChildCount();
             for (int i = 0; i < count; i++) {
                 View child = mShortcutsAndWidgets.getChildAt(i);
-                LayoutParams lp = (LayoutParams) child.getLayoutParams();
+                CellLayoutLayoutParams
+                        lp = (CellLayoutLayoutParams) child.getLayoutParams();
                 if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
                     lp.tmpCellX = lp.cellX;
                     lp.tmpCellY = lp.cellY;
@@ -2685,7 +2699,8 @@
      */
     void onDropChild(View child) {
         if (child != null) {
-            LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            CellLayoutLayoutParams
+                    lp = (CellLayoutLayoutParams) child.getLayoutParams();
             lp.dropped = true;
             child.requestLayout();
             markCellsAsOccupiedForView(child);
@@ -2727,7 +2742,8 @@
             return;
         }
         if (view == null || view.getParent() != mShortcutsAndWidgets) return;
-        LayoutParams lp = (LayoutParams) view.getLayoutParams();
+        CellLayoutLayoutParams
+                lp = (CellLayoutLayoutParams) view.getLayoutParams();
         mOccupied.markCells(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, true);
     }
 
@@ -2739,7 +2755,8 @@
             return;
         }
         if (view == null || view.getParent() != mShortcutsAndWidgets) return;
-        LayoutParams lp = (LayoutParams) view.getLayoutParams();
+        CellLayoutLayoutParams
+                lp = (CellLayoutLayoutParams) view.getLayoutParams();
         mOccupied.markCells(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, false);
     }
 
@@ -2763,165 +2780,17 @@
 
     @Override
     public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
-        return new CellLayout.LayoutParams(getContext(), attrs);
+        return new CellLayoutLayoutParams(getContext(), attrs);
     }
 
     @Override
     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
-        return p instanceof CellLayout.LayoutParams;
+        return p instanceof CellLayoutLayoutParams;
     }
 
     @Override
     protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
-        return new CellLayout.LayoutParams(p);
-    }
-
-    public static class LayoutParams extends ViewGroup.MarginLayoutParams {
-        /**
-         * Horizontal location of the item in the grid.
-         */
-        @ViewDebug.ExportedProperty
-        public int cellX;
-
-        /**
-         * Vertical location of the item in the grid.
-         */
-        @ViewDebug.ExportedProperty
-        public int cellY;
-
-        /**
-         * Temporary horizontal location of the item in the grid during reorder
-         */
-        public int tmpCellX;
-
-        /**
-         * Temporary vertical location of the item in the grid during reorder
-         */
-        public int tmpCellY;
-
-        /**
-         * Indicates that the temporary coordinates should be used to layout the items
-         */
-        public boolean useTmpCoords;
-
-        /**
-         * Number of cells spanned horizontally by the item.
-         */
-        @ViewDebug.ExportedProperty
-        public int cellHSpan;
-
-        /**
-         * Number of cells spanned vertically by the item.
-         */
-        @ViewDebug.ExportedProperty
-        public int cellVSpan;
-
-        /**
-         * Indicates whether the item will set its x, y, width and height parameters freely,
-         * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
-         */
-        public boolean isLockedToGrid = true;
-
-        /**
-         * Indicates whether this item can be reordered. Always true except in the case of the
-         * the AllApps button and QSB place holder.
-         */
-        public boolean canReorder = true;
-
-        // X coordinate of the view in the layout.
-        @ViewDebug.ExportedProperty
-        public int x;
-        // Y coordinate of the view in the layout.
-        @ViewDebug.ExportedProperty
-        public int y;
-
-        boolean dropped;
-
-        public LayoutParams(Context c, AttributeSet attrs) {
-            super(c, attrs);
-            cellHSpan = 1;
-            cellVSpan = 1;
-        }
-
-        public LayoutParams(ViewGroup.LayoutParams source) {
-            super(source);
-            cellHSpan = 1;
-            cellVSpan = 1;
-        }
-
-        public LayoutParams(LayoutParams source) {
-            super(source);
-            this.cellX = source.cellX;
-            this.cellY = source.cellY;
-            this.cellHSpan = source.cellHSpan;
-            this.cellVSpan = source.cellVSpan;
-        }
-
-        public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
-            super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
-            this.cellX = cellX;
-            this.cellY = cellY;
-            this.cellHSpan = cellHSpan;
-            this.cellVSpan = cellVSpan;
-        }
-
-        public void setup(int cellWidth, int cellHeight, boolean invertHorizontally, int colCount,
-                int rowCount, Point borderSpace, @Nullable Rect inset) {
-            setup(cellWidth, cellHeight, invertHorizontally, colCount, rowCount, 1.0f, 1.0f,
-                    borderSpace, inset);
-        }
-
-        /**
-         * Use this method, as opposed to {@link #setup(int, int, boolean, int, int, Point, Rect)},
-         * if the view needs to be scaled.
-         *
-         * ie. In multi-window mode, we setup widgets so that they are measured and laid out
-         * using their full/invariant device profile sizes.
-         */
-        public void setup(int cellWidth, int cellHeight, boolean invertHorizontally, int colCount,
-                int rowCount, float cellScaleX, float cellScaleY, Point borderSpace,
-                @Nullable Rect inset) {
-            if (isLockedToGrid) {
-                final int myCellHSpan = cellHSpan;
-                final int myCellVSpan = cellVSpan;
-                int myCellX = useTmpCoords ? tmpCellX : cellX;
-                int myCellY = useTmpCoords ? tmpCellY : cellY;
-
-                if (invertHorizontally) {
-                    myCellX = colCount - myCellX - cellHSpan;
-                }
-
-                int hBorderSpacing = (myCellHSpan - 1) * borderSpace.x;
-                int vBorderSpacing = (myCellVSpan - 1) * borderSpace.y;
-
-                float myCellWidth = ((myCellHSpan * cellWidth) + hBorderSpacing) / cellScaleX;
-                float myCellHeight = ((myCellVSpan * cellHeight) + vBorderSpacing) / cellScaleY;
-
-                width = Math.round(myCellWidth) - leftMargin - rightMargin;
-                height = Math.round(myCellHeight) - topMargin - bottomMargin;
-                x = leftMargin + (myCellX * cellWidth) + (myCellX * borderSpace.x);
-                y = topMargin + (myCellY * cellHeight) + (myCellY * borderSpace.y);
-
-                if (inset != null) {
-                    x -= inset.left;
-                    y -= inset.top;
-                    width += inset.left + inset.right;
-                    height += inset.top + inset.bottom;
-                }
-            }
-        }
-
-        /**
-         * Sets the position to the provided point
-         */
-        public void setCellXY(Point point) {
-            cellX = point.x;
-            cellY = point.y;
-        }
-
-        public String toString() {
-            return "(" + this.cellX + ", " + this.cellY + ")";
-        }
+        return new CellLayoutLayoutParams(p);
     }
 
     // This class stores info for two purposes:
diff --git a/src/com/android/launcher3/FastScrollRecyclerView.java b/src/com/android/launcher3/FastScrollRecyclerView.java
index 2f927d3..747b755 100644
--- a/src/com/android/launcher3/FastScrollRecyclerView.java
+++ b/src/com/android/launcher3/FastScrollRecyclerView.java
@@ -24,6 +24,7 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 
 import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.launcher3.compat.AccessibilityManagerCompat;
@@ -91,7 +92,8 @@
     protected int getAvailableScrollHeight() {
         // AvailableScrollHeight = Total height of the all items - first page height
         int firstPageHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
-        int availableScrollHeight = computeVerticalScrollRange() - firstPageHeight;
+        int totalHeightOfAllItems = getItemsHeight(/* untilIndex= */ getAdapter().getItemCount());
+        int availableScrollHeight = totalHeightOfAllItems - firstPageHeight;
         return Math.max(0, availableScrollHeight);
     }
 
@@ -144,7 +146,10 @@
 
         // IF scroller is at the very top OR there is no scroll bar because there is probably not
         // enough items to scroll, THEN it's okay for the container to be pulled down.
-        return computeVerticalScrollOffset() == 0;
+        if (getCurrentScrollY() == 0) {
+            return true;
+        }
+        return getAdapter() == null || getAdapter().getItemCount() == 0;
     }
 
     /**
@@ -155,6 +160,53 @@
     }
 
     /**
+     * @return the scroll top of this recycler view.
+     */
+    public int getCurrentScrollY() {
+        Adapter adapter = getAdapter();
+        if (adapter == null) {
+            return -1;
+        }
+        if (adapter.getItemCount() == 0 || getChildCount() == 0) {
+            return -1;
+        }
+
+        int itemPosition = NO_POSITION;
+        View child = null;
+
+        LayoutManager layoutManager = getLayoutManager();
+        if (layoutManager instanceof LinearLayoutManager) {
+            // Use the LayoutManager as the source of truth for visible positions. During
+            // animations, the view group child may not correspond to the visible views that appear
+            // at the top.
+            itemPosition = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();
+            child = layoutManager.findViewByPosition(itemPosition);
+        }
+
+        if (child == null) {
+            // If the layout manager returns null for any reason, which can happen before layout
+            // has occurred for the position, then look at the child of this view as a ViewGroup.
+            child = getChildAt(0);
+            itemPosition = getChildAdapterPosition(child);
+        }
+        if (itemPosition == NO_POSITION) {
+            return -1;
+        }
+        return getPaddingTop() + getItemsHeight(itemPosition)
+                - layoutManager.getDecoratedTop(child);
+    }
+
+    /**
+     * Returns the sum of the height, in pixels, of this list adapter's items from index
+     * 0 (inclusive) until {@code untilIndex} (exclusive). If untilIndex is same as the itemCount,
+     * it returns the full height of all the items.
+     *
+     * <p>If the untilIndex is larger than the total number of items in this adapter, returns the
+     * sum of all items' height.
+     */
+    protected abstract int getItemsHeight(int untilIndex);
+
+    /**
      * Maps the touch (from 0..1) to the adapter position that should be visible.
      * <p>Override in each subclass of this base class.
      */
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 447745e..5a6b3ec 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -2790,7 +2790,7 @@
             View v = getFirstMatch(Collections.singletonList(activeRecyclerView),
                     preferredItem, packageAndUserAndApp);
 
-            if (v != null && activeRecyclerView.computeVerticalScrollOffset() > 0) {
+            if (v != null && activeRecyclerView.getCurrentScrollY() > 0) {
                 RectF locationBounds = new RectF();
                 FloatingIconView.getLocationBoundsForView(this, v, false, locationBounds,
                         new Rect());
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 377843c..eb68adb 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -772,9 +772,9 @@
         }
 
         if (mScroller.isFinished() && pageScrollChanged) {
-            // TODO(b/238461210): Remove logging once root cause of flake detected.
+            // TODO(b/246283207): Remove logging once root cause of flake detected.
             if (Utilities.IS_RUNNING_IN_TEST_HARNESS && !(this instanceof Workspace)) {
-                Log.d("b/238461210", this.getClass().getSimpleName() + "#onLayout() -> "
+                Log.d("b/246283207", this.getClass().getSimpleName() + "#onLayout() -> "
                         + "if(mScroller.isFinished() && pageScrollChanged) -> getNextPage(): "
                         + getNextPage() + ", getScrollForPage(getNextPage()): "
                         + getScrollForPage(getNextPage()));
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index 486a68f..8b342ea 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -32,6 +32,7 @@
 import android.view.ViewGroup;
 
 import com.android.launcher3.CellLayout.ContainerType;
+import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.views.ActivityContext;
@@ -80,7 +81,7 @@
         final int count = getChildCount();
         for (int i = 0; i < count; i++) {
             View child = getChildAt(i);
-            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+            CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
 
             if ((lp.cellX <= cellX) && (cellX < lp.cellX + lp.cellHSpan)
                     && (lp.cellY <= cellY) && (cellY < lp.cellY + lp.cellVSpan)) {
@@ -107,7 +108,7 @@
     }
 
     public void setupLp(View child) {
-        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+        CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
         if (child instanceof NavigableAppWidgetHostView) {
             DeviceProfile profile = mActivity.getDeviceProfile();
             ((NavigableAppWidgetHostView) child).getWidgetInset(profile, mTempRect);
@@ -131,7 +132,7 @@
     }
 
     public void measureChild(View child) {
-        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+        CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
         final DeviceProfile dp = mActivity.getDeviceProfile();
 
         if (child instanceof NavigableAppWidgetHostView) {
@@ -175,7 +176,6 @@
         for (int i = 0; i < count; i++) {
             final View child = getChildAt(i);
             if (child.getVisibility() != GONE) {
-                CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
                 layoutChild(child);
             }
         }
@@ -185,7 +185,7 @@
      * Core logic to layout a child for this ViewGroup.
      */
     public void layoutChild(View child) {
-        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+        CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
         if (child instanceof NavigableAppWidgetHostView) {
             NavigableAppWidgetHostView nahv = (NavigableAppWidgetHostView) child;
 
@@ -255,7 +255,7 @@
 
     @Override
     public void drawFolderLeaveBehindForIcon(FolderIcon child) {
-        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+        CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
         // While the folder is open, the position of the icon cannot change.
         lp.canReorder = false;
         if (mContainerType == HOTSEAT) {
@@ -266,7 +266,7 @@
 
     @Override
     public void clearFolderLeaveBehind(FolderIcon child) {
-        ((CellLayout.LayoutParams) child.getLayoutParams()).canReorder = true;
+        ((CellLayoutLayoutParams) child.getLayoutParams()).canReorder = true;
         if (mContainerType == HOTSEAT) {
             CellLayout cl = (CellLayout) getParent();
             cl.clearFolderLeaveBehind();
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index a5eb9e0..6aff83e 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -67,6 +67,7 @@
 import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dot.FolderDotInfo;
 import com.android.launcher3.dragndrop.DragController;
@@ -562,7 +563,7 @@
         }
 
         int cellHSpan = mLauncher.getDeviceProfile().inv.numSearchContainerColumns;
-        CellLayout.LayoutParams lp = new CellLayout.LayoutParams(0, 0, cellHSpan, 1);
+        CellLayoutLayoutParams lp = new CellLayoutLayoutParams(0, 0, cellHSpan, 1);
         lp.canReorder = false;
         if (!firstPage.addViewToCellLayout(mQsb, 0, R.id.search_container_workspace, lp, true)) {
             Log.e(TAG, "Failed to add to item at (0, 0) to CellLayout");
@@ -1805,7 +1806,7 @@
 
     boolean willCreateUserFolder(ItemInfo info, View dropOverView, boolean considerTimeout) {
         if (dropOverView != null) {
-            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
+            CellLayoutLayoutParams lp = (CellLayoutLayoutParams) dropOverView.getLayoutParams();
             if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY)) {
                 return false;
             }
@@ -1840,7 +1841,7 @@
     }
     boolean willAddToExistingUserFolder(ItemInfo dragInfo, View dropOverView) {
         if (dropOverView != null) {
-            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
+            CellLayoutLayoutParams lp = (CellLayoutLayoutParams) dropOverView.getLayoutParams();
             if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY)) {
                 return false;
             }
@@ -2057,7 +2058,7 @@
                     }
 
                     // update the item's position after drop
-                    CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
+                    CellLayoutLayoutParams lp = (CellLayoutLayoutParams) cell.getLayoutParams();
                     lp.cellX = lp.tmpCellX = mTargetCell[0];
                     lp.cellY = lp.tmpCellY = mTargetCell[1];
                     lp.cellHSpan = item.spanX;
@@ -2083,7 +2084,7 @@
                     }
 
                     // If we can't find a drop location, we return the item to its original position
-                    CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
+                    CellLayoutLayoutParams lp = (CellLayoutLayoutParams) cell.getLayoutParams();
                     mTargetCell[0] = lp.cellX;
                     mTargetCell[1] = lp.cellY;
                     CellLayout layout = (CellLayout) cell.getParent().getParent();
diff --git a/src/com/android/launcher3/WorkspaceLayoutManager.java b/src/com/android/launcher3/WorkspaceLayoutManager.java
index 7e6e1b6..0b3a62f 100644
--- a/src/com/android/launcher3/WorkspaceLayoutManager.java
+++ b/src/com/android/launcher3/WorkspaceLayoutManager.java
@@ -19,6 +19,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.model.data.ItemInfo;
@@ -111,11 +112,11 @@
         }
 
         ViewGroup.LayoutParams genericLp = child.getLayoutParams();
-        CellLayout.LayoutParams lp;
-        if (genericLp == null || !(genericLp instanceof CellLayout.LayoutParams)) {
-            lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
+        CellLayoutLayoutParams lp;
+        if (genericLp == null || !(genericLp instanceof CellLayoutLayoutParams)) {
+            lp = new CellLayoutLayoutParams(x, y, spanX, spanY);
         } else {
-            lp = (CellLayout.LayoutParams) genericLp;
+            lp = (CellLayoutLayoutParams) genericLp;
             lp.cellX = x;
             lp.cellY = y;
             lp.cellHSpan = spanX;
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 79214e8..dd47592 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -23,6 +23,7 @@
 import com.android.launcher3.PendingAddItemInfo;
 import com.android.launcher3.R;
 import com.android.launcher3.Workspace;
+import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.dragndrop.DragOptions.PreDragCondition;
 import com.android.launcher3.dragndrop.DragView;
@@ -244,7 +245,7 @@
     }
 
     private boolean performResizeAction(int action, View host, LauncherAppWidgetInfo info) {
-        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) host.getLayoutParams();
+        CellLayoutLayoutParams lp = (CellLayoutLayoutParams) host.getLayoutParams();
         CellLayout layout = (CellLayout) host.getParent().getParent();
         layout.markCellsAsUnoccupiedForView(host);
 
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 368a373..fe0230a 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -26,9 +26,7 @@
 import androidx.core.view.accessibility.AccessibilityRecordCompat;
 import androidx.recyclerview.widget.GridLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
-import androidx.recyclerview.widget.RecyclerView.Adapter;
 
-import com.android.launcher3.util.ScrollableLayoutManager;
 import com.android.launcher3.views.ActivityContext;
 
 import java.util.List;
@@ -68,10 +66,10 @@
     /**
      * A subclass of GridLayoutManager that overrides accessibility values during app search.
      */
-    public class AppsGridLayoutManager extends ScrollableLayoutManager {
+    public class AppsGridLayoutManager extends GridLayoutManager {
 
         public AppsGridLayoutManager(Context context) {
-            super(context);
+            super(context, 1, GridLayoutManager.VERTICAL, false);
         }
 
         @Override
@@ -131,15 +129,6 @@
             }
             return extraRows;
         }
-
-        @Override
-        protected int incrementTotalHeight(Adapter adapter, int position, int heightUntilLastPos) {
-            AllAppsGridAdapter.AdapterItem item = mApps.getAdapterItems().get(position);
-            // only account for the first icon in the row since they are the same size within a row
-            return (isIconViewType(item.viewType) && item.rowAppIndex != 0)
-                    ? heightUntilLastPos
-                    : (heightUntilLastPos + mCachedSizes.get(item.viewType));
-        }
     }
 
     @Override
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 3d06fb5..21a7dfb 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.allapps;
 
+import static android.view.View.MeasureSpec.UNSPECIFIED;
+
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_SCROLLED;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_VERTICAL_SWIPE_BEGIN;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_VERTICAL_SWIPE_END;
@@ -24,6 +26,7 @@
 import android.graphics.Canvas;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.util.SparseIntArray;
 
 import androidx.recyclerview.widget.RecyclerView;
 
@@ -46,10 +49,40 @@
     private static final boolean DEBUG = false;
     private static final boolean DEBUG_LATENCY = Utilities.isPropertyEnabled(SEARCH_LOGGING);
 
+    protected AlphabeticalAppsList<?> mApps;
     protected final int mNumAppsPerRow;
+
+    // The specific view heights that we use to calculate scroll
+    private final SparseIntArray mViewHeights = new SparseIntArray();
+    private final SparseIntArray mCachedScrollPositions = new SparseIntArray();
     private final AllAppsFastScrollHelper mFastScrollHelper;
 
-    protected AlphabeticalAppsList<?> mApps;
+
+    private final AdapterDataObserver mObserver = new RecyclerView.AdapterDataObserver() {
+        public void onChanged() {
+            mCachedScrollPositions.clear();
+        }
+
+        @Override
+        public void onItemRangeChanged(int positionStart, int itemCount) {
+            onChanged();
+        }
+
+        @Override
+        public void onItemRangeInserted(int positionStart, int itemCount) {
+            onChanged();
+        }
+
+        @Override
+        public void onItemRangeRemoved(int positionStart, int itemCount) {
+            onChanged();
+        }
+
+        @Override
+        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
+            onChanged();
+        }
+    };
 
     public AllAppsRecyclerView(Context context) {
         this(context, null);
@@ -89,8 +122,12 @@
         pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_ALL_APPS_DIVIDER, 1);
         pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_ICON, approxRows
                 * (mNumAppsPerRow + 1));
+
+        mViewHeights.clear();
+        mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_ICON, grid.allAppsCellHeightPx);
     }
 
+
     @Override
     public void onDraw(Canvas c) {
         if (DEBUG) {
@@ -163,6 +200,17 @@
     }
 
     @Override
+    public void setAdapter(Adapter adapter) {
+        if (getAdapter() != null) {
+            getAdapter().unregisterAdapterDataObserver(mObserver);
+        }
+        super.setAdapter(adapter);
+        if (adapter != null) {
+            adapter.registerAdapterDataObserver(mObserver);
+        }
+    }
+
+    @Override
     protected boolean isPaddingOffsetRequired() {
         return true;
     }
@@ -183,13 +231,13 @@
         List<AllAppsGridAdapter.AdapterItem> items = mApps.getAdapterItems();
 
         // Skip early if there are no items or we haven't been measured
-        if (items.isEmpty() || mNumAppsPerRow == 0 || getChildCount() == 0) {
+        if (items.isEmpty() || mNumAppsPerRow == 0) {
             mScrollbar.setThumbOffsetY(-1);
             return;
         }
 
         // Skip early if, there no child laid out in the container.
-        int scrollY = computeVerticalScrollOffset();
+        int scrollY = getCurrentScrollY();
         if (scrollY < 0) {
             mScrollbar.setThumbOffsetY(-1);
             return;
@@ -244,6 +292,51 @@
         }
     }
 
+    @Override
+    protected int getItemsHeight(int position) {
+        List<AllAppsGridAdapter.AdapterItem> items = mApps.getAdapterItems();
+        AllAppsGridAdapter.AdapterItem posItem = position < items.size()
+                ? items.get(position) : null;
+        int y = mCachedScrollPositions.get(position, -1);
+        if (y < 0) {
+            y = 0;
+            for (int i = 0; i < position; i++) {
+                AllAppsGridAdapter.AdapterItem item = items.get(i);
+                if (AllAppsGridAdapter.isIconViewType(item.viewType)) {
+                    // Break once we reach the desired row
+                    if (posItem != null && posItem.viewType == item.viewType &&
+                            posItem.rowIndex == item.rowIndex) {
+                        break;
+                    }
+                    // Otherwise, only account for the first icon in the row since they are the same
+                    // size within a row
+                    if (item.rowAppIndex == 0) {
+                        y += mViewHeights.get(item.viewType, 0);
+                    }
+                } else {
+                    // Rest of the views span the full width
+                    int elHeight = mViewHeights.get(item.viewType);
+                    if (elHeight == 0) {
+                        ViewHolder holder = findViewHolderForAdapterPosition(i);
+                        if (holder == null) {
+                            holder = getAdapter().createViewHolder(this, item.viewType);
+                            getAdapter().onBindViewHolder(holder, i);
+                            holder.itemView.measure(UNSPECIFIED, UNSPECIFIED);
+                            elHeight = holder.itemView.getMeasuredHeight();
+
+                            getRecycledViewPool().putRecycledView(holder);
+                        } else {
+                            elHeight = holder.itemView.getMeasuredHeight();
+                        }
+                    }
+                    y += elHeight;
+                }
+            }
+            mCachedScrollPositions.put(position, y);
+        }
+        return y;
+    }
+
     public int getScrollBarTop() {
         return getResources().getDimensionPixelOffset(R.dimen.all_apps_header_top_padding);
     }
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
index 7f6247e..70c1e18 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
@@ -106,8 +106,7 @@
             new RecyclerView.OnScrollListener() {
                 @Override
                 public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
-                    updateHeaderScroll(
-                            ((AllAppsRecyclerView) recyclerView).computeVerticalScrollOffset());
+                    updateHeaderScroll(((AllAppsRecyclerView) recyclerView).getCurrentScrollY());
                 }
             };
 
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index 1cbb0f9..f31379e 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -68,7 +68,7 @@
                         mAnimator.cancel();
                     }
 
-                    int current = -mCurrentRV.computeVerticalScrollOffset();
+                    int current = -mCurrentRV.getCurrentScrollY();
                     boolean headerCollapsed = mHeaderCollapsed;
                     moved(current);
                     applyVerticalMove();
diff --git a/src/com/android/launcher3/celllayout/CellLayoutLayoutParams.java b/src/com/android/launcher3/celllayout/CellLayoutLayoutParams.java
new file mode 100644
index 0000000..b14ae8d
--- /dev/null
+++ b/src/com/android/launcher3/celllayout/CellLayoutLayoutParams.java
@@ -0,0 +1,183 @@
+/*
+ * 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.celllayout;
+
+import android.content.Context;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.ViewDebug;
+import android.view.ViewGroup;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Represents the logic of where a view is in a CellLayout and its size
+ */
+public class CellLayoutLayoutParams extends ViewGroup.MarginLayoutParams {
+    /**
+     * Horizontal location of the item in the grid.
+     */
+    @ViewDebug.ExportedProperty
+    public int cellX;
+
+    /**
+     * Vertical location of the item in the grid.
+     */
+    @ViewDebug.ExportedProperty
+    public int cellY;
+
+    /**
+     * Temporary horizontal location of the item in the grid during reorder
+     */
+    public int tmpCellX;
+
+    /**
+     * Temporary vertical location of the item in the grid during reorder
+     */
+    public int tmpCellY;
+
+    /**
+     * Indicates that the temporary coordinates should be used to layout the items
+     */
+    public boolean useTmpCoords;
+
+    /**
+     * Number of cells spanned horizontally by the item.
+     */
+    @ViewDebug.ExportedProperty
+    public int cellHSpan;
+
+    /**
+     * Number of cells spanned vertically by the item.
+     */
+    @ViewDebug.ExportedProperty
+    public int cellVSpan;
+
+    /**
+     * Indicates whether the item will set its x, y, width and height parameters freely,
+     * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
+     */
+    public boolean isLockedToGrid = true;
+
+    /**
+     * Indicates whether this item can be reordered. Always true except in the case of the
+     * the AllApps button and QSB place holder.
+     */
+    public boolean canReorder = true;
+
+    // X coordinate of the view in the layout.
+    @ViewDebug.ExportedProperty
+    public int x;
+    // Y coordinate of the view in the layout.
+    @ViewDebug.ExportedProperty
+    public int y;
+
+    public boolean dropped;
+
+    public CellLayoutLayoutParams(Context c, AttributeSet attrs) {
+        super(c, attrs);
+        cellHSpan = 1;
+        cellVSpan = 1;
+    }
+
+    public CellLayoutLayoutParams(ViewGroup.LayoutParams source) {
+        super(source);
+        cellHSpan = 1;
+        cellVSpan = 1;
+    }
+
+    public CellLayoutLayoutParams(CellLayoutLayoutParams source) {
+        super(source);
+        this.cellX = source.cellX;
+        this.cellY = source.cellY;
+        this.cellHSpan = source.cellHSpan;
+        this.cellVSpan = source.cellVSpan;
+    }
+
+    public CellLayoutLayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
+        super(CellLayoutLayoutParams.MATCH_PARENT, CellLayoutLayoutParams.MATCH_PARENT);
+        this.cellX = cellX;
+        this.cellY = cellY;
+        this.cellHSpan = cellHSpan;
+        this.cellVSpan = cellVSpan;
+    }
+
+    /**
+     * Updates the {@link CellLayoutLayoutParams} with the right measures using their
+     * full/invariant device profile sizes.
+     */
+    public void setup(int cellWidth, int cellHeight, boolean invertHorizontally, int colCount,
+            int rowCount, Point borderSpace, @Nullable Rect inset) {
+        setup(cellWidth, cellHeight, invertHorizontally, colCount, rowCount, 1.0f, 1.0f,
+                borderSpace, inset);
+    }
+
+    /**
+     * Use this method, as opposed to {@link #setup(int, int, boolean, int, int, Point, Rect)},
+     * if the view needs to be scaled.
+     *
+     * ie. In multi-window mode, we setup widgets so that they are measured and laid out
+     * using their full/invariant device profile sizes.
+     */
+    public void setup(int cellWidth, int cellHeight, boolean invertHorizontally, int colCount,
+            int rowCount, float cellScaleX, float cellScaleY, Point borderSpace,
+            @Nullable Rect inset) {
+        if (isLockedToGrid) {
+            final int myCellHSpan = cellHSpan;
+            final int myCellVSpan = cellVSpan;
+            int myCellX = useTmpCoords ? tmpCellX : cellX;
+            int myCellY = useTmpCoords ? tmpCellY : cellY;
+
+            if (invertHorizontally) {
+                myCellX = colCount - myCellX - cellHSpan;
+            }
+
+            int hBorderSpacing = (myCellHSpan - 1) * borderSpace.x;
+            int vBorderSpacing = (myCellVSpan - 1) * borderSpace.y;
+
+            float myCellWidth = ((myCellHSpan * cellWidth) + hBorderSpacing) / cellScaleX;
+            float myCellHeight = ((myCellVSpan * cellHeight) + vBorderSpacing) / cellScaleY;
+
+            width = Math.round(myCellWidth) - leftMargin - rightMargin;
+            height = Math.round(myCellHeight) - topMargin - bottomMargin;
+            x = leftMargin + (myCellX * cellWidth) + (myCellX * borderSpace.x);
+            y = topMargin + (myCellY * cellHeight) + (myCellY * borderSpace.y);
+
+            if (inset != null) {
+                x -= inset.left;
+                y -= inset.top;
+                width += inset.left + inset.right;
+                height += inset.top + inset.bottom;
+            }
+        }
+    }
+
+    /**
+     * Sets the position to the provided point
+     */
+    public void setCellXY(Point point) {
+        cellX = point.x;
+        cellY = point.y;
+    }
+
+    /**
+     * @return the string representation of the position of the {@link CellLayoutLayoutParams}
+     */
+    public String toString() {
+        return "(" + this.cellX + ", " + this.cellY + ")";
+    }
+}
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 030ac7e..3a530af 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -241,7 +241,7 @@
             "Enables One Search box in Taskbar All Apps.");
 
     public static final BooleanFlag ENABLE_TASKBAR_IN_OVERVIEW = getDebugFlag(
-            "ENABLE_TASKBAR_IN_OVERVIEW", true,
+            "ENABLE_TASKBAR_IN_OVERVIEW", false,
             "Enables accessing the system Taskbar in overview.");
 
     public static final BooleanFlag ENABLE_SPLIT_FROM_WORKSPACE = getDebugFlag(
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 8eeca7d..4bea0ad 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -43,7 +43,6 @@
 import android.view.animation.Interpolator;
 
 import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.CellLayout;
 import com.android.launcher3.DropTargetBar;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
@@ -51,6 +50,7 @@
 import com.android.launcher3.Workspace;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.anim.SpringProperty;
+import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.graphics.Scrim;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
@@ -237,7 +237,7 @@
             View anchorView) {
 
         ShortcutAndWidgetContainer parentChildren = (ShortcutAndWidgetContainer) child.getParent();
-        CellLayout.LayoutParams lp =  (CellLayout.LayoutParams) child.getLayoutParams();
+        CellLayoutLayoutParams lp =  (CellLayoutLayoutParams) child.getLayoutParams();
         parentChildren.measureChild(child);
         parentChildren.layoutChild(child);
 
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index 61ffd9d..d5ef9df 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -43,6 +43,7 @@
 import com.android.launcher3.ShortcutAndWidgetContainer;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.PropertyResetListener;
+import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.BaseDragLayer;
 
@@ -215,6 +216,7 @@
         final int footerStartDelay;
         if (isLargeFolder()) {
             if (mIsOpening) {
+                mFolder.mFooter.setAlpha(0);
                 footerAlphaDuration = LARGE_FOLDER_FOOTER_DURATION;
                 footerStartDelay = mDuration - footerAlphaDuration;
             } else {
@@ -341,7 +343,7 @@
         ShortcutAndWidgetContainer cwc = mContent.getPageAt(0).getShortcutsAndWidgets();
         for (int i = 0; i < numItemsInPreview; ++i) {
             final BubbleTextView btv = itemsInPreview.get(i);
-            CellLayout.LayoutParams btvLp = (CellLayout.LayoutParams) btv.getLayoutParams();
+            CellLayoutLayoutParams btvLp = (CellLayoutLayoutParams) btv.getLayoutParams();
 
             // Calculate the final values in the LayoutParams.
             btvLp.isLockedToGrid = true;
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 33e0902..e8759da 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -57,6 +57,7 @@
 import com.android.launcher3.Workspace;
 import com.android.launcher3.allapps.ActivityAllAppsContainerView;
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.dot.FolderDotInfo;
 import com.android.launcher3.dragndrop.BaseItemDragListener;
 import com.android.launcher3.dragndrop.DragLayer;
@@ -277,7 +278,7 @@
 
     public void onDragEnter(ItemInfo dragInfo) {
         if (mFolder.isDestroyed() || !willAcceptItem(dragInfo)) return;
-        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
+        CellLayoutLayoutParams lp = (CellLayoutLayoutParams) getLayoutParams();
         CellLayout cl = (CellLayout) getParent().getParent();
 
         mBackground.animateToAccept(cl, lp.cellX, lp.cellY);
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index 3d5aef5..0bcb97a 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -41,6 +41,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutAndWidgetContainer;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -202,7 +203,7 @@
     public void addViewForRank(View view, WorkspaceItemInfo item, int rank) {
         int pageNo = rank / mOrganizer.getMaxItemsPerPage();
 
-        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
+        CellLayoutLayoutParams lp = (CellLayoutLayoutParams) view.getLayoutParams();
         lp.setCellXY(mOrganizer.getPosForRank(rank));
         getPageAt(pageNo).addViewToCellLayout(view, -1, item.getViewId(), lp, true);
     }
@@ -218,9 +219,9 @@
         textView.setOnClickListener(ItemClickHandler.INSTANCE);
         textView.setOnLongClickListener(mFolder);
         textView.setOnFocusChangeListener(mFocusIndicatorHelper);
-        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) textView.getLayoutParams();
+        CellLayoutLayoutParams lp = (CellLayoutLayoutParams) textView.getLayoutParams();
         if (lp == null) {
-            textView.setLayoutParams(new CellLayout.LayoutParams(
+            textView.setLayoutParams(new CellLayoutLayoutParams(
                     item.cellX, item.cellY, item.spanX, item.spanY));
         } else {
             lp.cellX = item.cellX;
@@ -314,7 +315,7 @@
             }
 
             if (v != null) {
-                CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
+                CellLayoutLayoutParams lp = (CellLayoutLayoutParams) v.getLayoutParams();
                 ItemInfo info = (ItemInfo) v.getTag();
                 lp.setCellXY(mOrganizer.getPosForRank(rank));
                 currentPage.addViewToCellLayout(v, -1, info.getViewId(), lp, true);
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index c1bab54..482e923 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -72,6 +72,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.WorkspaceLayoutManager;
+import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.icons.BaseIconFactory;
@@ -532,8 +533,8 @@
             CellLayout firstScreen = mWorkspaceScreens.get(FIRST_SCREEN_ID);
             View qsb = mHomeElementInflater.inflate(R.layout.qsb_preview, firstScreen,
                     false);
-            CellLayout.LayoutParams lp =
-                    new CellLayout.LayoutParams(0, 0, firstScreen.getCountX(), 1);
+            CellLayoutLayoutParams lp = new CellLayoutLayoutParams(0, 0, firstScreen.getCountX(),
+                    1);
             lp.canReorder = false;
             firstScreen.addViewToCellLayout(qsb, 0, R.id.search_container_workspace, lp, true);
         }
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index 269baf0..d3c9bc9 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -112,12 +112,12 @@
 
             case TestProtocol.REQUEST_APPS_LIST_SCROLL_Y: {
                 return getLauncherUIProperty(Bundle::putInt,
-                        l -> l.getAppsView().getActiveRecyclerView().computeVerticalScrollOffset());
+                        l -> l.getAppsView().getActiveRecyclerView().getCurrentScrollY());
             }
 
             case TestProtocol.REQUEST_WIDGETS_SCROLL_Y: {
                 return getLauncherUIProperty(Bundle::putInt,
-                        l -> WidgetsFullSheet.getWidgetsView(l).computeVerticalScrollOffset());
+                        l -> WidgetsFullSheet.getWidgetsView(l).getCurrentScrollY());
             }
 
             case TestProtocol.REQUEST_TARGET_INSETS: {
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index 55afc28..5efebaa 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -618,15 +618,17 @@
 
         if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) {
             if (isLandscape) {
-                outRect.right = outRect.left + (int) (outRect.width() * topLeftTaskPercent);
+                outRect.right = outRect.left + Math.round(outRect.width() * topLeftTaskPercent);
             } else {
-                outRect.bottom = outRect.top + (int) (outRect.height() * topLeftTaskPercent);
+                outRect.bottom = outRect.top + Math.round(outRect.height() * topLeftTaskPercent);
             }
         } else {
             if (isLandscape) {
-                outRect.left += (int) (outRect.width() * (topLeftTaskPercent + dividerBarPercent));
+                outRect.left += Math.round(outRect.width()
+                        * (topLeftTaskPercent + dividerBarPercent));
             } else {
-                outRect.top += (int) (outRect.height() * (topLeftTaskPercent + dividerBarPercent));
+                outRect.top += Math.round(outRect.height()
+                        * (topLeftTaskPercent + dividerBarPercent));
             }
         }
     }
@@ -639,7 +641,7 @@
         int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
         int dividerBar = Math.round(splitBoundsConfig.appsStackedVertically
                 ? splitBoundsConfig.dividerHeightPercent * totalThumbnailHeight
-                : splitBoundsConfig.dividerWidthPercent * totalThumbnailHeight);
+                : splitBoundsConfig.dividerWidthPercent * parentWidth);
         int primarySnapshotHeight;
         int primarySnapshotWidth;
         int secondarySnapshotHeight;
@@ -648,7 +650,7 @@
                 splitBoundsConfig.topTaskPercent : splitBoundsConfig.leftTaskPercent;
         if (dp.isLandscape) {
             primarySnapshotHeight = totalThumbnailHeight;
-            primarySnapshotWidth = (int) (parentWidth * taskPercent);
+            primarySnapshotWidth = Math.round(parentWidth * taskPercent);
 
             secondarySnapshotHeight = totalThumbnailHeight;
             secondarySnapshotWidth = parentWidth - primarySnapshotWidth - dividerBar;
@@ -663,7 +665,7 @@
             secondarySnapshot.setTranslationY(spaceAboveSnapshot);
         } else {
             primarySnapshotWidth = parentWidth;
-            primarySnapshotHeight = (int) (totalThumbnailHeight * taskPercent);
+            primarySnapshotHeight = Math.round(totalThumbnailHeight * taskPercent);
 
             secondarySnapshotWidth = parentWidth;
             secondarySnapshotHeight = totalThumbnailHeight - primarySnapshotHeight - dividerBar;
diff --git a/src/com/android/launcher3/util/DimensionUtils.kt b/src/com/android/launcher3/util/DimensionUtils.kt
new file mode 100644
index 0000000..758b3a9
--- /dev/null
+++ b/src/com/android/launcher3/util/DimensionUtils.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.content.res.Resources
+import android.graphics.Point
+import android.view.ViewGroup
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.R
+
+object DimensionUtils {
+    /**
+     * Point where x is width, and y is height of taskbar based on provided [deviceProfile]
+     * x or y could also be -1 to indicate there is no dimension specified
+     */
+    @JvmStatic
+    fun getTaskbarPhoneDimensions(deviceProfile: DeviceProfile, res: Resources,
+                                  isPhoneMode: Boolean): Point {
+        val p = Point()
+        // Taskbar for large screen
+        if (!isPhoneMode) {
+            p.x = ViewGroup.LayoutParams.MATCH_PARENT
+            p.y = deviceProfile.taskbarSize
+            return p
+        }
+
+        // Taskbar on phone using gesture nav, it will always be stashed
+        if (deviceProfile.isGestureMode) {
+            p.x = ViewGroup.LayoutParams.MATCH_PARENT
+            p.y = res.getDimensionPixelSize(R.dimen.taskbar_stashed_size)
+            return p
+        }
+
+        // Taskbar on phone, portrait
+        if (!deviceProfile.isLandscape) {
+            p.x = ViewGroup.LayoutParams.MATCH_PARENT
+            p.y = res.getDimensionPixelSize(R.dimen.taskbar_size)
+            return p
+        }
+
+        // Taskbar on phone, landscape
+        p.x = res.getDimensionPixelSize(R.dimen.taskbar_size)
+        p.y = ViewGroup.LayoutParams.MATCH_PARENT
+        return p
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/util/ScrollableLayoutManager.java b/src/com/android/launcher3/util/ScrollableLayoutManager.java
deleted file mode 100644
index 17eaefd..0000000
--- a/src/com/android/launcher3/util/ScrollableLayoutManager.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * 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.content.Context;
-import android.util.SparseIntArray;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.recyclerview.widget.GridLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.recyclerview.widget.RecyclerView.Adapter;
-import androidx.recyclerview.widget.RecyclerView.State;
-import androidx.recyclerview.widget.RecyclerView.ViewHolder;
-
-/**
- * Extension of {@link GridLayoutManager} with support for smooth scrolling
- */
-public class ScrollableLayoutManager extends GridLayoutManager {
-
-    // keyed on item type
-    protected final SparseIntArray mCachedSizes = new SparseIntArray();
-
-    private RecyclerView mRv;
-
-    /**
-     * Precalculated total height keyed on the item position. This is always incremental.
-     * Subclass can override {@link #incrementTotalHeight} to incorporate the layout logic.
-     * For example all-apps should have same values for items in same row,
-     *     sample values: 0, 10, 10, 10, 10, 20, 20, 20, 20
-     * whereas widgets will have strictly increasing values
-     *     sample values: 0, 10, 50, 60, 110
-     */
-
-    //
-    private int[] mTotalHeightCache = new int[1];
-    private int mLastValidHeightIndex = 0;
-
-    public ScrollableLayoutManager(Context context) {
-        super(context, 1, GridLayoutManager.VERTICAL, false);
-    }
-
-    @Override
-    public void onAttachedToWindow(RecyclerView view) {
-        super.onAttachedToWindow(view);
-        mRv = view;
-    }
-
-    @Override
-    public void layoutDecorated(@NonNull View child, int left, int top, int right, int bottom) {
-        super.layoutDecorated(child, left, top, right, bottom);
-        mCachedSizes.put(
-                mRv.getChildViewHolder(child).getItemViewType(), child.getMeasuredHeight());
-    }
-
-    @Override
-    public void layoutDecoratedWithMargins(@NonNull View child, int left, int top, int right,
-            int bottom) {
-        super.layoutDecoratedWithMargins(child, left, top, right, bottom);
-        mCachedSizes.put(
-                mRv.getChildViewHolder(child).getItemViewType(), child.getMeasuredHeight());
-    }
-
-    @Override
-    public int computeVerticalScrollExtent(State state) {
-        return mRv == null ? 0 : mRv.getHeight();
-    }
-
-    @Override
-    public int computeVerticalScrollOffset(State state) {
-        Adapter adapter = mRv == null ? null : mRv.getAdapter();
-        if (adapter == null) {
-            return 0;
-        }
-        if (adapter.getItemCount() == 0 || getChildCount() == 0) {
-            return 0;
-        }
-        View child = getChildAt(0);
-        ViewHolder holder = mRv.findContainingViewHolder(child);
-        if (holder == null) {
-            return 0;
-        }
-        int itemPosition = holder.getLayoutPosition();
-        if (itemPosition < 0) {
-            return 0;
-        }
-        return getPaddingTop() + getItemsHeight(adapter, itemPosition) - getDecoratedTop(child);
-    }
-
-    @Override
-    public int computeVerticalScrollRange(State state) {
-        Adapter adapter = mRv == null ? null : mRv.getAdapter();
-        return adapter == null ? 0 : getItemsHeight(adapter, adapter.getItemCount());
-    }
-
-    /**
-     * Returns the sum of the height, in pixels, of this list adapter's items from index
-     * 0 (inclusive) until {@code untilIndex} (exclusive). If untilIndex is same as the itemCount,
-     * it returns the full height of all the items.
-     *
-     * <p>If the untilIndex is larger than the total number of items in this adapter, returns the
-     * sum of all items' height.
-     */
-    private int getItemsHeight(Adapter adapter, int untilIndex) {
-        final int totalItems = adapter.getItemCount();
-        if (mTotalHeightCache.length < (totalItems + 1)) {
-            mTotalHeightCache = new int[totalItems + 1];
-            mLastValidHeightIndex = 0;
-        }
-        if (untilIndex > totalItems) {
-            untilIndex = totalItems;
-        } else if (untilIndex < 0) {
-            untilIndex = 0;
-        }
-        if (untilIndex <= mLastValidHeightIndex) {
-            return mTotalHeightCache[untilIndex];
-        }
-
-        int totalItemsHeight = mTotalHeightCache[mLastValidHeightIndex];
-        for (int i = mLastValidHeightIndex; i < untilIndex; i++) {
-            totalItemsHeight = incrementTotalHeight(adapter, i, totalItemsHeight);
-            mTotalHeightCache[i + 1] = totalItemsHeight;
-        }
-        mLastValidHeightIndex = untilIndex;
-        return totalItemsHeight;
-    }
-
-    /**
-     * The current implementation assumes a linear list with every item taking up the whole row.
-     * Subclasses should override this method to account for any spanning logic
-     */
-    protected int incrementTotalHeight(Adapter adapter, int position, int heightUntilLastPos) {
-        return heightUntilLastPos + mCachedSizes.get(adapter.getItemViewType(position));
-    }
-
-    private void invalidateScrollCache() {
-        mLastValidHeightIndex = 0;
-    }
-
-    @Override
-    public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
-        super.onItemsAdded(recyclerView, positionStart, itemCount);
-        invalidateScrollCache();
-    }
-
-    @Override
-    public void onItemsChanged(RecyclerView recyclerView) {
-        super.onItemsChanged(recyclerView);
-        invalidateScrollCache();
-    }
-
-    @Override
-    public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
-        super.onItemsRemoved(recyclerView, positionStart, itemCount);
-        invalidateScrollCache();
-    }
-
-    @Override
-    public void onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount) {
-        super.onItemsMoved(recyclerView, from, to, itemCount);
-        invalidateScrollCache();
-    }
-
-    @Override
-    public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount,
-            Object payload) {
-        super.onItemsUpdated(recyclerView, positionStart, itemCount, payload);
-        invalidateScrollCache();
-    }
-}
diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
index 3af2e3c..40e4ce1 100644
--- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java
+++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
@@ -119,6 +119,7 @@
     // prevent jumping, this offset is applied as the user scrolls.
     protected int mTouchOffsetY;
     protected int mThumbOffsetY;
+    protected int mRvOffsetY;
 
     // Fast scroller popup
     private TextView mPopupView;
@@ -206,11 +207,16 @@
 
     public void setThumbOffsetY(int y) {
         if (mThumbOffsetY == y) {
+            int rvCurrentOffsetY = mRv.getCurrentScrollY();
+            if (mRvOffsetY != rvCurrentOffsetY) {
+                mRvOffsetY = mRv.getCurrentScrollY();
+            }
             return;
         }
         updatePopupY(y);
         mThumbOffsetY = y;
         invalidate();
+        mRvOffsetY = mRv.getCurrentScrollY();
     }
 
     public int getThumbOffsetY() {
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
index 5969e3e..35fa7a4 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.graphics.Point;
 import android.util.AttributeSet;
+import android.util.SparseIntArray;
 import android.view.MotionEvent;
 
 import androidx.recyclerview.widget.LinearLayoutManager;
@@ -27,7 +28,6 @@
 
 import com.android.launcher3.FastScrollRecyclerView;
 import com.android.launcher3.R;
-import com.android.launcher3.util.ScrollableLayoutManager;
 
 /**
  * The widgets recycler view.
@@ -42,6 +42,14 @@
     private boolean mTouchDownOnScroller;
     private HeaderViewDimensionsProvider mHeaderViewDimensionsProvider;
 
+    /**
+     * There is always 1 or 0 item of VIEW_TYPE_WIDGETS_LIST. Other types have fixes sizes, so the
+     * the size can be used for all other items of same type. Caching the last know size for
+     * VIEW_TYPE_WIDGETS_LIST allows us to use it to estimate full size even when
+     * VIEW_TYPE_WIDGETS_LIST is not visible on the screen.
+     */
+    private final SparseIntArray mCachedSizes = new SparseIntArray();
+
     public WidgetsRecyclerView(Context context) {
         this(context, null);
     }
@@ -60,7 +68,9 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        setLayoutManager(new ScrollableLayoutManager(getContext()));
+        // create a layout manager with Launcher's context so that scroll position
+        // can be preserved during screen rotation.
+        setLayoutManager(new LinearLayoutManager(getContext()));
     }
 
     @Override
@@ -104,7 +114,7 @@
         }
 
         // Skip early if, there no child laid out in the container.
-        int scrollY = computeVerticalScrollOffset();
+        int scrollY = getCurrentScrollY();
         if (scrollY < 0) {
             mScrollbar.setThumbOffsetY(-1);
             return;
@@ -154,6 +164,39 @@
     }
 
     /**
+     * Returns the sum of the height, in pixels, of this list adapter's items from index 0 until
+     * {@code untilIndex}.
+     *
+     * <p>If the untilIndex is larger than the total number of items in this adapter, returns the
+     * sum of all items' height.
+     */
+    @Override
+    protected int getItemsHeight(int untilIndex) {
+        // Initialize cache
+        int childCount = getChildCount();
+        int startPosition;
+        if (childCount > 0
+                && ((startPosition = getChildAdapterPosition(getChildAt(0))) != NO_POSITION)) {
+            int loopCount = Math.min(getChildCount(), getAdapter().getItemCount() - startPosition);
+            for (int i = 0; i < loopCount; i++) {
+                mCachedSizes.put(
+                        mAdapter.getItemViewType(startPosition + i),
+                        getChildAt(i).getMeasuredHeight());
+            }
+        }
+
+        if (untilIndex > mAdapter.getItems().size()) {
+            untilIndex = mAdapter.getItems().size();
+        }
+        int totalItemsHeight = 0;
+        for (int i = 0; i < untilIndex; i++) {
+            int type = mAdapter.getItemViewType(i);
+            totalItemsHeight += mCachedSizes.get(type);
+        }
+        return totalItemsHeight;
+    }
+
+    /**
      * Provides dimensions of the header view that is shown at the top of a
      * {@link WidgetsRecyclerView}.
      */
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 70d122b..32b62fb 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -537,7 +537,7 @@
     }
 
     protected int getAllAppsScroll(Launcher launcher) {
-        return launcher.getAppsView().getActiveRecyclerView().computeVerticalScrollOffset();
+        return launcher.getAppsView().getActiveRecyclerView().getCurrentScrollY();
     }
 
     private void checkLauncherIntegrity(
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 07bfe4c..a3eee00 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -18,8 +18,6 @@
 
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
-import android.platform.test.annotations.IwTest;
-
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
@@ -31,6 +29,7 @@
 
 import android.content.Intent;
 import android.graphics.Point;
+import android.platform.test.annotations.IwTest;
 
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -303,7 +302,7 @@
     }
 
     private int getWidgetsScroll(Launcher launcher) {
-        return getWidgetsView(launcher).computeVerticalScrollOffset();
+        return getWidgetsView(launcher).getCurrentScrollY();
     }
 
     private boolean isOptionsPopupVisible(Launcher launcher) {
@@ -437,6 +436,23 @@
 
     @Test
     @PortraitLandscape
+    public void testDragAndCancelAppIcon() {
+        final HomeAppIcon homeAppIcon = createShortcutInCenterIfNotExist(GMAIL_APP_NAME);
+        Point positionBeforeDrag =
+                mLauncher.getWorkspace().getWorkspaceIconsPositions().get(GMAIL_APP_NAME);
+        assertNotNull("App not found in Workspace before dragging.", positionBeforeDrag);
+
+        mLauncher.getWorkspace().dragAndCancelAppIcon(homeAppIcon);
+
+        Point positionAfterDrag =
+                mLauncher.getWorkspace().getWorkspaceIconsPositions().get(GMAIL_APP_NAME);
+        assertNotNull("App not found in Workspace after dragging.", positionAfterDrag);
+        assertEquals("App not returned to same position in Workspace after drag & cancel",
+                positionBeforeDrag, positionAfterDrag);
+    }
+
+    @Test
+    @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}) {
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index d765c2f..41bb7f3 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -137,6 +137,15 @@
         OUTSIDE_WITH_KEYCODE,
     }
 
+    /**
+     * Represents a point in the code at which a callback can run.
+     */
+    public enum CALLBACK_RUN_POINT {
+        CALLBACK_HOLD_BEFORE_DROP
+    }
+
+    private Consumer<CALLBACK_RUN_POINT> mCallbackAtRunPoint = null;
+
     // Base class for launcher containers.
     abstract static class VisibleContainer {
         protected final LauncherInstrumentation mLauncher;
@@ -788,7 +797,7 @@
                     waitUntilLauncherObjectGone(APPS_RES_ID);
                     waitUntilLauncherObjectGone(WORKSPACE_RES_ID);
                     waitUntilLauncherObjectGone(WIDGETS_RES_ID);
-                    checkTaskbarVisibility();
+                    waitUntilLauncherObjectGone(TASKBAR_RES_ID);
                     waitUntilLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
 
                     return waitForLauncherObject(OVERVIEW_RES_ID);
@@ -797,7 +806,7 @@
                     waitUntilLauncherObjectGone(APPS_RES_ID);
                     waitUntilLauncherObjectGone(WORKSPACE_RES_ID);
                     waitUntilLauncherObjectGone(WIDGETS_RES_ID);
-                    checkTaskbarVisibility();
+                    waitUntilLauncherObjectGone(TASKBAR_RES_ID);
 
                     waitForLauncherObject(SPLIT_PLACEHOLDER_RES_ID);
                     return waitForLauncherObject(OVERVIEW_RES_ID);
@@ -806,7 +815,7 @@
                     waitUntilLauncherObjectGone(APPS_RES_ID);
                     waitUntilLauncherObjectGone(WORKSPACE_RES_ID);
                     waitUntilLauncherObjectGone(WIDGETS_RES_ID);
-                    checkTaskbarVisibility();
+                    waitUntilLauncherObjectGone(TASKBAR_RES_ID);
                     waitUntilLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
 
                     return waitForFallbackLauncherObject(OVERVIEW_RES_ID);
@@ -818,7 +827,14 @@
                     waitUntilLauncherObjectGone(WIDGETS_RES_ID);
                     waitUntilLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
 
-                    checkTaskbarVisibility();
+                    if (mIgnoreTaskbarVisibility) {
+                        return null;
+                    }
+                    if (isTablet() && !isFallbackOverview()) {
+                        waitForLauncherObject(TASKBAR_RES_ID);
+                    } else {
+                        waitUntilLauncherObjectGone(TASKBAR_RES_ID);
+                    }
                     return null;
                 }
                 default:
@@ -828,18 +844,6 @@
         }
     }
 
-    private void checkTaskbarVisibility() {
-        if (mIgnoreTaskbarVisibility) {
-            return;
-        }
-
-        if (isTablet() && !isFallbackOverview()) {
-            waitForLauncherObject(TASKBAR_RES_ID);
-        } else {
-            waitUntilLauncherObjectGone(TASKBAR_RES_ID);
-        }
-    }
-
     public void waitForLauncherInitialized() {
         for (int i = 0; i < 100; ++i) {
             if (getTestInfo(
@@ -1963,4 +1967,20 @@
                     LauncherInstrumentation.GestureScope.INSIDE);
         }
     }
+
+    /**
+     * Sets the consumer to run callbacks at all run-points.
+     */
+    public void setRunPointCallback(Consumer<CALLBACK_RUN_POINT> callback) {
+        mCallbackAtRunPoint = callback;
+    }
+
+    /**
+     * Runs the callback at the specified point if it exists.
+     */
+    void runCallbackIfActive(CALLBACK_RUN_POINT runPoint) {
+        if (mCallbackAtRunPoint != null) {
+            mCallbackAtRunPoint.accept(runPoint);
+        }
+    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 2c9fdb3..425a90a 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -18,6 +18,7 @@
 
 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_SCROLLED;
 
+import static com.android.launcher3.tapl.LauncherInstrumentation.CALLBACK_RUN_POINT.CALLBACK_HOLD_BEFORE_DROP;
 import static com.android.launcher3.testing.shared.TestProtocol.ALL_APPS_STATE_ORDINAL;
 import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
 
@@ -302,6 +303,31 @@
     }
 
     /**
+     * Drag the appIcon from the workspace and cancel by dragging icon to corner of screen where no
+     * drop point exists.
+     *
+     * @param homeAppIcon to be dragged.
+     */
+    @NonNull
+    public Workspace dragAndCancelAppIcon(HomeAppIcon homeAppIcon) {
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+             LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                     "dragging app icon across workspace")) {
+            dragIconToWorkspace(
+                    mLauncher,
+                    homeAppIcon,
+                    () -> new Point(0, 0),
+                    () -> mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, LONG_CLICK_EVENT),
+                    null);
+
+            try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+                    "dragged the app across workspace")) {
+                return new Workspace(mLauncher);
+            }
+        }
+    }
+
+    /**
      * Delete the appIcon from the workspace.
      *
      * @param homeAppIcon to be deleted.
@@ -493,6 +519,7 @@
             launcher.movePointer(dragStart, targetDest, DEFAULT_DRAG_STEPS, isDecelerating,
                     downTime, SystemClock.uptimeMillis(), false,
                     LauncherInstrumentation.GestureScope.INSIDE);
+            launcher.runCallbackIfActive(CALLBACK_HOLD_BEFORE_DROP);
             dropDraggedIcon(launcher, targetDest, downTime, expectDropEvents);
         }
     }