Merge "Fix preference screen title to make folded devices the same as phone" into tm-qpr-dev
diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto
index 677c992..c8a7d85 100644
--- a/protos/launcher_atom.proto
+++ b/protos/launcher_atom.proto
@@ -130,7 +130,7 @@
   optional int32 cardinality = 2;
 }
 
-// Next value 41
+// Next value 43
 enum Attribute {
   UNKNOWN = 0;
   DEFAULT_LAYOUT = 1;       // icon automatically placed in workspace, folder, hotseat
@@ -174,6 +174,8 @@
   ALL_APPS_SEARCH_RESULT_PEOPLE_TILE = 27;
   ALL_APPS_SEARCH_RESULT_LEGACY_SHORTCUT = 30;
   ALL_APPS_SEARCH_RESULT_ASSISTANT_MEMORY = 31;
+  ALL_APPS_SEARCH_RESULT_VIDEO = 41;
+  ALL_APPS_SEARCH_RESULT_SYSTEM_POINTER = 42;
 
   // Web suggestions provided by AGA
   ALL_APPS_SEARCH_RESULT_WEB_SUGGEST = 39;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
index d0059f7..7114849 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
@@ -194,7 +194,8 @@
     public boolean dispatchKeyEvent(KeyEvent event) {
         if (event.getAction() == ACTION_UP && event.getKeyCode() == KEYCODE_BACK) {
             AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
-            if (topView != null && topView.onBackPressed()) {
+            if (topView != null && topView.canHandleBack()) {
+                topView.onBackInvoked();
                 // Handled by the floating view.
                 return true;
             }
diff --git a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayDragLayer.java
index 044afd6..d91b650 100644
--- a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayDragLayer.java
+++ b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayDragLayer.java
@@ -71,7 +71,8 @@
     public boolean dispatchKeyEvent(KeyEvent event) {
         if (event.getAction() == ACTION_UP && event.getKeyCode() == KEYCODE_BACK) {
             AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
-            if (topView != null && topView.onBackPressed()) {
+            if (topView != null && topView.canHandleBack()) {
+                topView.onBackInvoked();
                 return true;
             }
         }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 0673dc6..30850b9 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -84,6 +84,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.OnBackPressedHandler;
 import com.android.launcher3.QuickstepAccessibilityDelegate;
 import com.android.launcher3.QuickstepTransitionManager;
 import com.android.launcher3.R;
@@ -638,20 +639,49 @@
         getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
                 OnBackInvokedDispatcher.PRIORITY_DEFAULT,
                 new OnBackAnimationCallback() {
+
+                    @Nullable OnBackPressedHandler mActiveOnBackPressedHandler;
+
+                    @Override
+                    public void onBackStarted(@NonNull BackEvent backEvent) {
+                        if (mActiveOnBackPressedHandler != null) {
+                            mActiveOnBackPressedHandler.onBackCancelled();
+                        }
+                        mActiveOnBackPressedHandler = getOnBackPressedHandler();
+                        mActiveOnBackPressedHandler.onBackStarted();
+                    }
+
                     @Override
                     public void onBackInvoked() {
-                        onBackPressed();
+                        // Recreate mActiveOnBackPressedHandler if necessary to avoid NPE because:
+                        // 1. b/260636433: In 3-button-navigation mode, onBackStarted() is not
+                        // called on ACTION_DOWN before onBackInvoked() is called in ACTION_UP.
+                        // 2. Launcher#onBackPressed() will call onBackInvoked() without calling
+                        // onBackInvoked() beforehand.
+                        if (mActiveOnBackPressedHandler == null) {
+                            mActiveOnBackPressedHandler = getOnBackPressedHandler();
+                        }
+                        mActiveOnBackPressedHandler.onBackInvoked();
+                        mActiveOnBackPressedHandler = null;
                         TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onBackInvoked");
                     }
 
                     @Override
                     public void onBackProgressed(@NonNull BackEvent backEvent) {
-                        QuickstepLauncher.this.onBackProgressed(backEvent.getProgress());
+                        if (!FeatureFlags.IS_STUDIO_BUILD && mActiveOnBackPressedHandler == null) {
+                            return;
+                        }
+                        mActiveOnBackPressedHandler
+                                .onBackProgressed(backEvent.getProgress());
                     }
 
                     @Override
                     public void onBackCancelled() {
-                        QuickstepLauncher.this.onBackCancelled();
+                        if (!FeatureFlags.IS_STUDIO_BUILD && mActiveOnBackPressedHandler == null) {
+                            return;
+                        }
+                        mActiveOnBackPressedHandler.onBackCancelled();
+                        mActiveOnBackPressedHandler = null;
                     }
                 });
     }
diff --git a/quickstep/src/com/android/quickstep/views/AllAppsEduView.java b/quickstep/src/com/android/quickstep/views/AllAppsEduView.java
index d79b318..716d389 100644
--- a/quickstep/src/com/android/quickstep/views/AllAppsEduView.java
+++ b/quickstep/src/com/android/quickstep/views/AllAppsEduView.java
@@ -112,11 +112,6 @@
     }
 
     @Override
-    public boolean onBackPressed() {
-        return true;
-    }
-
-    @Override
     public boolean canInterceptEventsInSystemGestureRegion() {
         return true;
     }
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 49afc1f..796fa80 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -46,7 +46,8 @@
 /**
  * Base class for a View which shows a floating UI on top of the launcher UI.
  */
-public abstract class AbstractFloatingView extends LinearLayout implements TouchController {
+public abstract class AbstractFloatingView extends LinearLayout implements TouchController,
+        OnBackPressedHandler {
 
     @IntDef(flag = true, value = {
             TYPE_FOLDER,
@@ -165,13 +166,17 @@
 
     protected abstract boolean isOfType(@FloatingViewType int type);
 
-    /** @return Whether the back is consumed. If false, Launcher will handle the back as well. */
-    public boolean onBackPressed() {
-        close(true);
+    /** Return true if this view can consume back press. */
+    public boolean canHandleBack() {
         return true;
     }
 
     @Override
+    public void onBackInvoked() {
+        close(true);
+    }
+
+    @Override
     public boolean onControllerTouchEvent(MotionEvent ev) {
         return false;
     }
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index ca1fe40..6f3e948 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -125,9 +125,13 @@
         mCurrentActionMode = null;
     }
 
+    protected boolean isInAutoCancelActionMode() {
+        return mCurrentActionMode != null && AUTO_CANCEL_ACTION_MODE == mCurrentActionMode.getTag();
+    }
+
     @Override
     public boolean finishAutoCancelActionMode() {
-        if (mCurrentActionMode != null && AUTO_CANCEL_ACTION_MODE == mCurrentActionMode.getTag()) {
+        if (isInAutoCancelActionMode()) {
             mCurrentActionMode.finish();
             return true;
         }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index de5419e..e9b9f31 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -132,7 +132,6 @@
 import com.android.launcher3.allapps.AllAppsRecyclerView;
 import com.android.launcher3.allapps.AllAppsStore;
 import com.android.launcher3.allapps.AllAppsTransitionController;
-import com.android.launcher3.allapps.BaseAllAppsContainerView;
 import com.android.launcher3.allapps.BaseSearchConfig;
 import com.android.launcher3.allapps.DiscoveryBounce;
 import com.android.launcher3.anim.PropertyListBuilder;
@@ -560,6 +559,61 @@
         setTitle(R.string.home_screen);
     }
 
+    /**
+     * Provide {@link OnBackPressedHandler} in below order:
+     * <ol>
+     *  <li> auto cancel action mode handler
+     *  <li> drag handler
+     *  <li> view handler
+     *  <li> state handler
+     * </ol>
+     *
+     * A back gesture (a single click on back button, or a swipe back gesture that contains a series
+     * of swipe events) should be handled by the same handler from above list. For a new back
+     * gesture, a new handler should be regenerated.
+     *
+     * Note that state handler will always be handling the back press event if the previous 3 don't.
+     */
+    @NonNull
+    protected OnBackPressedHandler getOnBackPressedHandler() {
+        // #1 auto cancel action mode handler
+        if (isInAutoCancelActionMode()) {
+            return this::finishAutoCancelActionMode;
+        }
+
+        // #2 drag handler
+        if (mDragController.isDragging()) {
+            return mDragController::cancelDrag;
+        }
+
+        // #3 view handler
+        AbstractFloatingView topView =
+                AbstractFloatingView.getTopOpenView(Launcher.this);
+        if (topView != null && topView.canHandleBack()) {
+            return topView;
+        }
+
+        // #4 state handler
+        return new OnBackPressedHandler() {
+            @Override
+            public void onBackInvoked() {
+                onStateBack();
+            }
+
+            @Override
+            public void onBackProgressed(
+                    @FloatRange(from = 0.0, to = 1.0) float backProgress) {
+                mStateManager.getState().onBackProgressed(
+                        Launcher.this, backProgress);
+            }
+
+            @Override
+            public void onBackCancelled() {
+                mStateManager.getState().onBackCancelled(Launcher.this);
+            }
+        };
+    }
+
     protected LauncherOverlayManager getDefaultOverlay() {
         return new LauncherOverlayManager() { };
     }
@@ -1642,7 +1696,7 @@
 
     private void showAllAppsWorkTabFromIntent(boolean alreadyOnHome) {
         showAllAppsFromIntent(alreadyOnHome);
-        mAppsView.switchToTab(BaseAllAppsContainerView.AdapterHolder.WORK);
+        mAppsView.switchToTab(ActivityAllAppsContainerView.AdapterHolder.WORK);
     }
 
     /**
@@ -2047,36 +2101,13 @@
 
     @Override
     public void onBackPressed() {
-        if (finishAutoCancelActionMode()) {
-            return;
-        }
-
-        if (mDragController.isDragging()) {
-            mDragController.cancelDrag();
-            return;
-        }
-
-        // Note: There should be at most one log per method call. This is enforced implicitly
-        // by using if-else statements.
-        AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this);
-        if (topView == null || !topView.onBackPressed()) {
-            // Not handled by the floating view.
-            onStateBack();
-        }
+        getOnBackPressedHandler().onBackInvoked();
     }
 
     protected void onStateBack() {
         mStateManager.getState().onBackPressed(this);
     }
 
-    protected void onBackProgressed(@FloatRange(from = 0.0, to = 1.0) float backProgress) {
-        mStateManager.getState().onBackProgressed(this, backProgress);
-    }
-
-    protected void onBackCancelled() {
-        mStateManager.getState().onBackCancelled(this);
-    }
-
     protected void onScreenOnChanged(boolean isOn) {
         // Reset AllApps to its initial state only if we are not in the middle of
         // processing a multi-step drop
diff --git a/src/com/android/launcher3/OnBackPressedHandler.java b/src/com/android/launcher3/OnBackPressedHandler.java
new file mode 100644
index 0000000..485aa0a
--- /dev/null
+++ b/src/com/android/launcher3/OnBackPressedHandler.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 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;
+
+import androidx.annotation.FloatRange;
+
+/**
+ * Interface that mimics {@link android.window.OnBackInvokedCallback} without dependencies on U's
+ * API such as {@link android.window.BackEvent}.
+ *
+ * <p> Impl can assume below order during a back gesture:
+ * <ol>
+ *  <li> [optional] one {@link #onBackStarted()} will be called to start the gesture
+ *  <li> zero or multiple {@link #onBackProgressed(float)} will be called during swipe gesture
+ *  <li> either one of {@link #onBackInvoked()} or {@link #onBackCancelled()} will be called to end
+ *  the gesture
+ */
+public interface OnBackPressedHandler {
+
+    /** Called when back has started. */
+    default void onBackStarted() {}
+
+    /** Called when back is committed. */
+    void onBackInvoked();
+
+    /** Called with back gesture's progress. */
+    default void onBackProgressed(@FloatRange(from = 0.0, to = 1.0) float backProgress) {}
+
+    /** Called when user drops the back gesture. */
+    default void onBackCancelled() {}
+}
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index 2511cada..95e61b8 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -15,25 +15,74 @@
  */
 package com.android.launcher3.allapps;
 
-import static com.android.launcher3.allapps.BaseAllAppsContainerView.AdapterHolder.SEARCH;
+import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.SEARCH;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_COUNT;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_TAP_ON_PERSONAL_TAB;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_TAP_ON_WORK_TAB;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
 import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Path.Direction;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.os.Process;
+import android.os.UserManager;
 import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.TypedValue;
 import android.view.KeyEvent;
+import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.WindowInsets;
+import android.widget.Button;
 import android.widget.RelativeLayout;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 import androidx.core.graphics.ColorUtils;
 import androidx.recyclerview.widget.RecyclerView;
 
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
+import com.android.launcher3.DragSource;
+import com.android.launcher3.DropTarget.DragObject;
+import com.android.launcher3.Insettable;
+import com.android.launcher3.InsettableFrameLayout;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
+import com.android.launcher3.allapps.search.DefaultSearchAdapterProvider;
+import com.android.launcher3.allapps.search.SearchAdapterProvider;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.keyboard.FocusedItemDecorator;
+import com.android.launcher3.model.StringCache;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.views.BaseDragLayer;
+import com.android.launcher3.views.RecyclerViewFastScroller;
+import com.android.launcher3.views.ScrimView;
+import com.android.launcher3.views.SpringRelativeLayout;
+import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
 
 /**
  * All apps container view with search support for use in a dragging activity.
@@ -41,16 +90,67 @@
  * @param <T> Type of context inflating all apps.
  */
 public class ActivityAllAppsContainerView<T extends Context & ActivityContext>
-        extends BaseAllAppsContainerView<T> {
+        extends SpringRelativeLayout implements DragSource, Insettable,
+        OnDeviceProfileChangeListener, PersonalWorkSlidingTabStrip.OnActivePageChangedListener,
+        ScrimView.ScrimDrawingController {
 
+    public static final float PULL_MULTIPLIER = .02f;
+    public static final float FLING_VELOCITY_MULTIPLIER = 1200f;
+    protected static final String BUNDLE_KEY_CURRENT_PAGE = "launcher.allapps.current_page";
     private static final long DEFAULT_SEARCH_TRANSITION_DURATION_MS = 300;
+    // Render the header protection at all times to debug clipping issues.
+    private static final boolean DEBUG_HEADER_PROTECTION = false;
+    /** Context of an activity or window that is inflating this container. */
+
+    protected final T mActivityContext;
+    protected final List<AdapterHolder> mAH;
+    protected final Predicate<ItemInfo> mPersonalMatcher = ItemInfoMatcher.ofUser(
+            Process.myUserHandle());
+    protected final WorkProfileManager mWorkManager;
+    protected final Point mFastScrollerOffset = new Point();
+    protected final int mScrimColor;
+    protected final float mHeaderThreshold;
 
     // Used to animate Search results out and A-Z apps in, or vice-versa.
     private final SearchTransitionController mSearchTransitionController;
+    private final Paint mHeaderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+    private final Rect mInsets = new Rect();
+    private final AllAppsStore mAllAppsStore = new AllAppsStore();
+    private final RecyclerView.OnScrollListener mScrollListener =
+            new RecyclerView.OnScrollListener() {
+                @Override
+                public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
+                    updateHeaderScroll(recyclerView.computeVerticalScrollOffset());
+                }
+            };
+    private final Paint mNavBarScrimPaint;
+    private final int mHeaderProtectionColor;
+    private final Path mTmpPath = new Path();
+    private final RectF mTmpRectF = new RectF();
+    protected AllAppsPagedView mViewPager;
+    protected FloatingHeaderView mHeader;
+    protected View mBottomSheetBackground;
+    /**
+     * View that defines the search box. Result is rendered inside {@link #mSearchRecyclerView}.
+     */
+    protected View mSearchContainer;
+    protected SearchUiManager mSearchUiManager;
+    protected boolean mUsingTabs;
+    protected RecyclerViewFastScroller mTouchHandler;
 
     /** {@code true} when rendered view is in search state instead of the scroll state. */
     private boolean mIsSearching;
     private boolean mRebindAdaptersAfterSearchAnimation;
+    private int mNavBarScrimHeight = 0;
+    private SearchRecyclerView mSearchRecyclerView;
+    private SearchAdapterProvider<?> mMainAdapterProvider;
+    private View mBottomSheetHandleArea;
+    private boolean mHasWorkApps;
+    private float[] mBottomSheetCornerRadii;
+    private ScrimView mScrimView;
+    private int mHeaderColor;
+    private int mBottomSheetBackgroundColor;
+    private int mTabsProtectionAlpha;
 
     public ActivityAllAppsContainerView(Context context) {
         this(context, null);
@@ -62,13 +162,84 @@
 
     public ActivityAllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
+        mActivityContext = ActivityContext.lookupContext(context);
+
+        mScrimColor = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
+        mHeaderThreshold = getResources().getDimensionPixelSize(
+                R.dimen.dynamic_grid_cell_border_spacing);
+        mHeaderProtectionColor = Themes.getAttrColor(context, R.attr.allappsHeaderProtectionColor);
+
+        mWorkManager = new WorkProfileManager(
+                mActivityContext.getSystemService(UserManager.class),
+                this, mActivityContext.getStatsLogManager());
+        mAH = Arrays.asList(null, null, null);
+        mNavBarScrimPaint = new Paint();
+        mNavBarScrimPaint.setColor(Themes.getAttrColor(context, R.attr.allAppsNavBarScrimColor));
+
+        mAllAppsStore.addUpdateListener(this::onAppsUpdated);
+        mActivityContext.addOnDeviceProfileChangeListener(this);
+
+        // This is a focus listener that proxies focus from a view into the list view.  This is to
+        // work around the search box from getting first focus and showing the cursor.
+        setOnFocusChangeListener((v, hasFocus) -> {
+            if (hasFocus && getActiveRecyclerView() != null) {
+                getActiveRecyclerView().requestFocus();
+            }
+        });
+        initContent();
 
         mSearchTransitionController = new SearchTransitionController(this);
     }
 
+    /**
+     * Initializes the view hierarchy and internal variables. Any initialization which actually uses
+     * these members should be done in {@link #onFinishInflate()}.
+     * In terms of subclass initialization, the following would be parallel order for activity:
+     *   initContent -> onPreCreate
+     *   constructor/init -> onCreate
+     *   onFinishInflate -> onPostCreate
+     */
+    protected void initContent() {
+        mMainAdapterProvider = createMainAdapterProvider();
+
+        mAH.set(AdapterHolder.MAIN, new AdapterHolder(AdapterHolder.MAIN));
+        mAH.set(AdapterHolder.WORK, new AdapterHolder(AdapterHolder.WORK));
+        mAH.set(SEARCH, new AdapterHolder(SEARCH));
+
+        getLayoutInflater().inflate(R.layout.all_apps_content, this);
+        mHeader = findViewById(R.id.all_apps_header);
+        mBottomSheetBackground = findViewById(R.id.bottom_sheet_background);
+        mBottomSheetHandleArea = findViewById(R.id.bottom_sheet_handle_area);
+        mSearchRecyclerView = findViewById(R.id.search_results_list_view);
+
+        // Add the search box next to the header
+        mSearchContainer = inflateSearchBox();
+        addView(mSearchContainer, indexOfChild(mHeader) + 1);
+        mSearchUiManager = (SearchUiManager) mSearchContainer;
+    }
+
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
+
+        mAH.get(SEARCH).setup(mSearchRecyclerView,
+                /* Filter out A-Z apps */ itemInfo -> false);
+        rebindAdapters(true /* force */);
+        float cornerRadius = Themes.getDialogCornerRadius(getContext());
+        mBottomSheetCornerRadii = new float[]{
+                cornerRadius,
+                cornerRadius, // Top left radius in px
+                cornerRadius,
+                cornerRadius, // Top right radius in px
+                0,
+                0, // Bottom right
+                0,
+                0 // Bottom left
+        };
+        final TypedValue value = new TypedValue();
+        getContext().getTheme().resolveAttribute(android.R.attr.colorBackground, value, true);
+        mBottomSheetBackgroundColor = value.data;
+        updateBackground(mActivityContext.getDeviceProfile());
         mSearchUiManager.initializeSearch(this);
     }
 
@@ -141,19 +312,43 @@
                 });
     }
 
-    @Override
     public boolean shouldContainerScroll(MotionEvent ev) {
+        BaseDragLayer dragLayer = mActivityContext.getDragLayer();
         // IF the MotionEvent is inside the search box, and the container keeps on receiving
         // touch input, container should move down.
-        if (mActivityContext.getDragLayer().isEventOverView(mSearchContainer, ev)) {
+        if (dragLayer.isEventOverView(mSearchContainer, ev)) {
             return true;
         }
-        return super.shouldContainerScroll(ev);
+        // Scroll if not within the container view (e.g. over large-screen scrim).
+        if (!dragLayer.isEventOverView(getVisibleContainerView(), ev)) {
+            return true;
+        }
+        if (dragLayer.isEventOverView(mBottomSheetHandleArea, ev)) {
+            return true;
+        }
+        AllAppsRecyclerView rv = getActiveRecyclerView();
+        if (rv == null) {
+            return true;
+        }
+        if (rv.getScrollbar() != null
+                && rv.getScrollbar().getThumbOffsetY() >= 0
+                && dragLayer.isEventOverView(rv.getScrollbar(), ev)) {
+            return false;
+        }
+        return rv.shouldContainerScroll(ev, dragLayer);
     }
 
-    @Override
     public void reset(boolean animate) {
-        super.reset(animate);
+        for (int i = 0; i < mAH.size(); i++) {
+            if (mAH.get(i).mRecyclerView != null) {
+                mAH.get(i).mRecyclerView.scrollToTop();
+            }
+        }
+        if (isHeaderVisible()) {
+            mHeader.reset(animate);
+        }
+        // Reset the base recycler view after transitioning home.
+        updateHeaderScroll(0);
         // Reset the search bar after transitioning home.
         mSearchUiManager.resetSearch();
         // Animate to A-Z with 0 time to reset the animation with proper state management.
@@ -166,16 +361,26 @@
         return super.dispatchKeyEvent(event);
     }
 
-    @Override
     public String getDescription() {
         if (!mUsingTabs && isSearching()) {
             return getContext().getString(R.string.all_apps_search_results);
         } else {
-            return super.getDescription();
+            StringCache cache = mActivityContext.getStringCache();
+            if (mUsingTabs) {
+                if (cache != null) {
+                    return isPersonalTab()
+                            ? cache.allAppsPersonalTabAccessibility
+                            : cache.allAppsWorkTabAccessibility;
+                } else {
+                    return isPersonalTab()
+                            ? getContext().getString(R.string.all_apps_button_personal_label)
+                            : getContext().getString(R.string.all_apps_button_work_label);
+                }
+            }
+            return getContext().getString(R.string.all_apps_button_label);
         }
     }
 
-    @Override
     public boolean isSearching() {
         return mIsSearching;
     }
@@ -186,30 +391,112 @@
             // Will be called at the end of the animation.
             return;
         }
-        super.onActivePageChanged(currentActivePage);
+        if (mAH.get(currentActivePage).mRecyclerView != null) {
+            mAH.get(currentActivePage).mRecyclerView.bindFastScrollbar();
+        }
+        // Header keeps track of active recycler view to properly render header protection.
+        mHeader.setActiveRV(currentActivePage);
+        reset(true /* animate */);
+
+        mWorkManager.onActivePageChanged(currentActivePage);
     }
 
-    @Override
+    protected void rebindAdapters() {
+        rebindAdapters(false /* force */);
+    }
+
     protected void rebindAdapters(boolean force) {
         if (mSearchTransitionController.isRunning()) {
             mRebindAdaptersAfterSearchAnimation = true;
             return;
         }
-        super.rebindAdapters(force);
-        if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get()
-                || getMainAdapterProvider().getDecorator() == null
-                || getSearchRecyclerView() == null) {
+        updateSearchResultsVisibility();
+
+        boolean showTabs = shouldShowTabs();
+        if (showTabs == mUsingTabs && !force) {
             return;
         }
 
-        RecyclerView.ItemDecoration decoration = getMainAdapterProvider().getDecorator();
-        getSearchRecyclerView().removeItemDecoration(decoration); // In case it is already added.
-        getSearchRecyclerView().addItemDecoration(decoration);
+        if (isSearching()) {
+            mUsingTabs = showTabs;
+            mWorkManager.detachWorkModeSwitch();
+            return;
+        }
+
+        // replaceAppsRVcontainer() needs to use both mUsingTabs value to remove the old view AND
+        // showTabs value to create new view. Hence the mUsingTabs new value assignment MUST happen
+        // after this call.
+        replaceAppsRVContainer(showTabs);
+        mUsingTabs = showTabs;
+
+        mAllAppsStore.unregisterIconContainer(mAH.get(AdapterHolder.MAIN).mRecyclerView);
+        mAllAppsStore.unregisterIconContainer(mAH.get(AdapterHolder.WORK).mRecyclerView);
+        mAllAppsStore.unregisterIconContainer(mAH.get(AdapterHolder.SEARCH).mRecyclerView);
+
+        if (mUsingTabs) {
+            mAH.get(AdapterHolder.MAIN).setup(mViewPager.getChildAt(0), mPersonalMatcher);
+            mAH.get(AdapterHolder.WORK).setup(mViewPager.getChildAt(1), mWorkManager.getMatcher());
+            mAH.get(AdapterHolder.WORK).mRecyclerView.setId(R.id.apps_list_view_work);
+            if (FeatureFlags.ENABLE_EXPANDING_PAUSE_WORK_BUTTON.get()) {
+                mAH.get(AdapterHolder.WORK).mRecyclerView.addOnScrollListener(
+                        mWorkManager.newScrollListener());
+            }
+            mViewPager.getPageIndicator().setActiveMarker(AdapterHolder.MAIN);
+            findViewById(R.id.tab_personal)
+                    .setOnClickListener((View view) -> {
+                        if (mViewPager.snapToPage(AdapterHolder.MAIN)) {
+                            mActivityContext.getStatsLogManager().logger()
+                                    .log(LAUNCHER_ALLAPPS_TAP_ON_PERSONAL_TAB);
+                        }
+                        mActivityContext.hideKeyboard();
+                    });
+            findViewById(R.id.tab_work)
+                    .setOnClickListener((View view) -> {
+                        if (mViewPager.snapToPage(AdapterHolder.WORK)) {
+                            mActivityContext.getStatsLogManager().logger()
+                                    .log(LAUNCHER_ALLAPPS_TAP_ON_WORK_TAB);
+                        }
+                        mActivityContext.hideKeyboard();
+                    });
+            setDeviceManagementResources();
+            onActivePageChanged(mViewPager.getNextPage());
+        } else {
+            mAH.get(AdapterHolder.MAIN).setup(findViewById(R.id.apps_list_view), null);
+            mAH.get(AdapterHolder.WORK).mRecyclerView = null;
+        }
+        setupHeader();
+
+        mAllAppsStore.registerIconContainer(mAH.get(AdapterHolder.MAIN).mRecyclerView);
+        mAllAppsStore.registerIconContainer(mAH.get(AdapterHolder.WORK).mRecyclerView);
+        mAllAppsStore.registerIconContainer(mAH.get(AdapterHolder.SEARCH).mRecyclerView);
     }
 
-    @Override
     protected View replaceAppsRVContainer(boolean showTabs) {
-        View rvContainer = super.replaceAppsRVContainer(showTabs);
+        for (int i = AdapterHolder.MAIN; i <= AdapterHolder.WORK; i++) {
+            AdapterHolder adapterHolder = mAH.get(i);
+            if (adapterHolder.mRecyclerView != null) {
+                adapterHolder.mRecyclerView.setLayoutManager(null);
+                adapterHolder.mRecyclerView.setAdapter(null);
+            }
+        }
+        View oldView = getAppsRecyclerViewContainer();
+        int index = indexOfChild(oldView);
+        removeView(oldView);
+        int layout = showTabs ? R.layout.all_apps_tabs : R.layout.all_apps_rv_layout;
+        final View rvContainer = getLayoutInflater().inflate(layout, this, false);
+        addView(rvContainer, index);
+        if (showTabs) {
+            mViewPager = (AllAppsPagedView) rvContainer;
+            mViewPager.initParentViews(this);
+            mViewPager.getPageIndicator().setOnActivePageChangedListener(this);
+
+            mWorkManager.reset();
+            post(() -> mAH.get(AdapterHolder.WORK).applyPadding());
+
+        } else {
+            mWorkManager.detachWorkModeSwitch();
+            mViewPager = null;
+        }
 
         removeCustomRules(rvContainer);
         removeCustomRules(getSearchRecyclerView());
@@ -228,21 +515,47 @@
         return rvContainer;
     }
 
-    @Override
     void setupHeader() {
-        super.setupHeader();
+        mHeader.setVisibility(View.VISIBLE);
+        boolean tabsHidden = !mUsingTabs;
+        mHeader.setup(
+                mAH.get(AdapterHolder.MAIN).mRecyclerView,
+                mAH.get(AdapterHolder.WORK).mRecyclerView,
+                (SearchRecyclerView) mAH.get(SEARCH).mRecyclerView,
+                getCurrentPage(),
+                tabsHidden);
+
+        int padding = mHeader.getMaxTranslation();
+        mAH.forEach(adapterHolder -> {
+            adapterHolder.mPadding.top = padding;
+            adapterHolder.applyPadding();
+            if (adapterHolder.mRecyclerView != null) {
+                adapterHolder.mRecyclerView.scrollToTop();
+            }
+        });
 
         removeCustomRules(mHeader);
-        if (FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get()) {
+        if (!isSearchSupported()) {
+            layoutWithoutSearchContainer(mHeader, false /* includeTabsMargin */);
+        } else if (FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get()) {
             alignParentTop(mHeader, false /* includeTabsMargin */);
         } else {
             layoutBelowSearchContainer(mHeader, false /* includeTabsMargin */);
         }
     }
 
-    @Override
     protected void updateHeaderScroll(int scrolledOffset) {
-        super.updateHeaderScroll(scrolledOffset);
+        float prog1 = Utilities.boundToRange((float) scrolledOffset / mHeaderThreshold, 0f, 1f);
+        int headerColor = getHeaderColor(prog1);
+        int tabsAlpha = mHeader.getPeripheralProtectionHeight() == 0 ? 0
+                : (int) (Utilities.boundToRange(
+                        (scrolledOffset + mHeader.mSnappedScrolledY) / mHeaderThreshold, 0f, 1f)
+                        * 255);
+        if (headerColor != mHeaderColor || mTabsProtectionAlpha != tabsAlpha) {
+            mHeaderColor = headerColor;
+            mTabsProtectionAlpha = tabsAlpha;
+            invalidateHeader();
+        }
         if (mSearchUiManager.getEditText() == null) {
             return;
         }
@@ -257,10 +570,9 @@
         mSearchUiManager.setBackgroundVisibility(bgVisible, 1 - prog);
     }
 
-    @Override
     protected int getHeaderColor(float blendRatio) {
         return ColorUtils.setAlphaComponent(
-                super.getHeaderColor(blendRatio),
+                ColorUtils.blendARGB(mScrimColor, mHeaderProtectionColor, blendRatio),
                 (int) (mSearchContainer.getAlpha() * 255));
     }
 
@@ -315,7 +627,6 @@
         layoutParams.removeRule(RelativeLayout.ALIGN_PARENT_TOP);
     }
 
-    @Override
     protected BaseAllAppsAdapter<T> createAdapter(AlphabeticalAppsList<T> appsList,
             BaseAdapterProvider[] adapterProviders) {
         return new AllAppsGridAdapter<>(mActivityContext, getLayoutInflater(), appsList,
@@ -339,9 +650,558 @@
                 : R.dimen.all_apps_header_top_margin);
     }
 
-    @Override
     public boolean isInAllApps() {
         // TODO: Make this abstract
         return true;
     }
+
+    /**
+     * Inflates the search box
+     */
+    protected View inflateSearchBox() {
+        return getLayoutInflater().inflate(R.layout.search_container_all_apps, this, false);
+    }
+
+    /** Creates the adapter provider for the main section. */
+    protected SearchAdapterProvider<?> createMainAdapterProvider() {
+        return new DefaultSearchAdapterProvider(mActivityContext);
+    }
+
+    /** The adapter provider for the main section. */
+    public final SearchAdapterProvider<?> getMainAdapterProvider() {
+        return mMainAdapterProvider;
+    }
+
+    @Override
+    protected void dispatchRestoreInstanceState(SparseArray<Parcelable> sparseArray) {
+        try {
+            // Many slice view id is not properly assigned, and hence throws null
+            // pointer exception in the underneath method. Catching the exception
+            // simply doesn't restore these slice views. This doesn't have any
+            // user visible effect because because we query them again.
+            super.dispatchRestoreInstanceState(sparseArray);
+        } catch (Exception e) {
+            Log.e("AllAppsContainerView", "restoreInstanceState viewId = 0", e);
+        }
+
+        Bundle state = (Bundle) sparseArray.get(R.id.work_tab_state_id, null);
+        if (state != null) {
+            int currentPage = state.getInt(BUNDLE_KEY_CURRENT_PAGE, 0);
+            if (currentPage == AdapterHolder.WORK && mViewPager != null) {
+                mViewPager.setCurrentPage(currentPage);
+                rebindAdapters();
+            } else {
+                reset(true);
+            }
+        }
+    }
+
+    @Override
+    protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
+        super.dispatchSaveInstanceState(container);
+        Bundle state = new Bundle();
+        state.putInt(BUNDLE_KEY_CURRENT_PAGE, getCurrentPage());
+        container.put(R.id.work_tab_state_id, state);
+    }
+
+    /**
+     * Sets the long click listener for icons
+     */
+    public void setOnIconLongClickListener(OnLongClickListener listener) {
+        for (AdapterHolder holder : mAH) {
+            holder.mAdapter.setOnIconLongClickListener(listener);
+        }
+    }
+
+    public AllAppsStore getAppsStore() {
+        return mAllAppsStore;
+    }
+
+    public WorkProfileManager getWorkManager() {
+        return mWorkManager;
+    }
+
+    @Override
+    public void onDeviceProfileChanged(DeviceProfile dp) {
+        for (AdapterHolder holder : mAH) {
+            holder.mAdapter.setAppsPerRow(dp.numShownAllAppsColumns);
+            if (holder.mRecyclerView != null) {
+                // Remove all views and clear the pool, while keeping the data same. After this
+                // call, all the viewHolders will be recreated.
+                holder.mRecyclerView.swapAdapter(holder.mRecyclerView.getAdapter(), true);
+                holder.mRecyclerView.getRecycledViewPool().clear();
+            }
+        }
+        updateBackground(dp);
+    }
+
+    protected void updateBackground(DeviceProfile deviceProfile) {
+        mBottomSheetBackground.setVisibility(deviceProfile.isTablet ? View.VISIBLE : View.GONE);
+        // Note: For tablets, the opaque background and header protection are added in drawOnScrim.
+        // For the taskbar entrypoint, the scrim is drawn differently, so a static background is
+        // added in TaskbarAllAppsContainerView and header protection is not yet supported.
+    }
+
+    private void onAppsUpdated() {
+        mHasWorkApps = Stream.of(mAllAppsStore.getApps()).anyMatch(mWorkManager.getMatcher());
+        if (!isSearching()) {
+            rebindAdapters();
+            if (mHasWorkApps) {
+                mWorkManager.reset();
+            }
+        }
+
+        mActivityContext.getStatsLogManager().logger()
+                .withCardinality(mAllAppsStore.getApps().length)
+                .log(LAUNCHER_ALLAPPS_COUNT);
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        // The AllAppsContainerView houses the QSB and is hence visible from the Workspace
+        // Overview states. We shouldn't intercept for the scrubber in these cases.
+        if (!isInAllApps()) {
+            mTouchHandler = null;
+            return false;
+        }
+
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            AllAppsRecyclerView rv = getActiveRecyclerView();
+            if (rv != null && rv.getScrollbar() != null
+                    && rv.getScrollbar().isHitInParent(ev.getX(), ev.getY(), mFastScrollerOffset)) {
+                mTouchHandler = rv.getScrollbar();
+            } else {
+                mTouchHandler = null;
+            }
+        }
+        if (mTouchHandler != null) {
+            return mTouchHandler.handleTouchEvent(ev, mFastScrollerOffset);
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        if (!isInAllApps()) {
+            return false;
+        }
+
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            AllAppsRecyclerView rv = getActiveRecyclerView();
+            if (rv != null && rv.getScrollbar() != null
+                    && rv.getScrollbar().isHitInParent(ev.getX(), ev.getY(), mFastScrollerOffset)) {
+                mTouchHandler = rv.getScrollbar();
+            } else {
+                mTouchHandler = null;
+
+            }
+        }
+        if (mTouchHandler != null) {
+            mTouchHandler.handleTouchEvent(ev, mFastScrollerOffset);
+            return true;
+        }
+        if (isSearching()
+                && mActivityContext.getDragLayer().isEventOverView(getVisibleContainerView(), ev)) {
+            // if in search state, consume touch event.
+            return true;
+        }
+        return false;
+    }
+
+    /** The current active recycler view (A-Z list from one of the profiles, or search results). */
+    public AllAppsRecyclerView getActiveRecyclerView() {
+        if (isSearching()) {
+            return getSearchRecyclerView();
+        }
+        return getActiveAppsRecyclerView();
+    }
+
+    /** The current apps recycler view in the container. */
+    private AllAppsRecyclerView getActiveAppsRecyclerView() {
+        if (!mUsingTabs || isPersonalTab()) {
+            return mAH.get(AdapterHolder.MAIN).mRecyclerView;
+        } else {
+            return mAH.get(AdapterHolder.WORK).mRecyclerView;
+        }
+    }
+
+    /**
+     * The container for A-Z apps (the ViewPager for main+work tabs, or main RV). This is currently
+     * hidden while searching.
+     **/
+    protected View getAppsRecyclerViewContainer() {
+        return mViewPager != null ? mViewPager : findViewById(R.id.apps_list_view);
+    }
+
+    /** The RV for search results, which is hidden while A-Z apps are visible. */
+    public SearchRecyclerView getSearchRecyclerView() {
+        return mSearchRecyclerView;
+    }
+
+    protected boolean isPersonalTab() {
+        return mViewPager == null || mViewPager.getNextPage() == 0;
+    }
+
+    /**
+     * Switches the current page to the provided {@code tab} if tabs are supported, otherwise does
+     * nothing.
+     */
+    public void switchToTab(int tab) {
+        if (mUsingTabs) {
+            mViewPager.setCurrentPage(tab);
+        }
+    }
+
+    public LayoutInflater getLayoutInflater() {
+        return LayoutInflater.from(getContext());
+    }
+
+    @Override
+    public void onDropCompleted(View target, DragObject d, boolean success) {}
+
+    @Override
+    public void setInsets(Rect insets) {
+        mInsets.set(insets);
+        DeviceProfile grid = mActivityContext.getDeviceProfile();
+
+        applyAdapterSideAndBottomPaddings(grid);
+
+        MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
+        mlp.leftMargin = insets.left;
+        mlp.rightMargin = insets.right;
+        setLayoutParams(mlp);
+
+        if (grid.isVerticalBarLayout()) {
+            setPadding(grid.workspacePadding.left, 0, grid.workspacePadding.right, 0);
+        } else {
+            int topPadding = grid.allAppsTopPadding;
+            if (FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get() && !grid.isTablet) {
+                topPadding += getResources().getDimensionPixelSize(
+                        R.dimen.all_apps_additional_top_padding_floating_search);
+            }
+            setPadding(grid.allAppsLeftRightMargin, topPadding, grid.allAppsLeftRightMargin, 0);
+        }
+
+        InsettableFrameLayout.dispatchInsets(this, insets);
+    }
+
+    /**
+     * Returns a padding in case a scrim is shown on the bottom of the view and a padding is needed.
+     */
+    protected int computeNavBarScrimHeight(WindowInsets insets) {
+        return 0;
+    }
+
+    /**
+     * Returns the current height of nav bar scrim
+     */
+    public int getNavBarScrimHeight() {
+        return mNavBarScrimHeight;
+    }
+
+    @Override
+    public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
+        mNavBarScrimHeight = computeNavBarScrimHeight(insets);
+        applyAdapterSideAndBottomPaddings(mActivityContext.getDeviceProfile());
+        return super.dispatchApplyWindowInsets(insets);
+    }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        super.dispatchDraw(canvas);
+
+        if (mNavBarScrimHeight > 0) {
+            canvas.drawRect(0, getHeight() - mNavBarScrimHeight, getWidth(), getHeight(),
+                    mNavBarScrimPaint);
+        }
+    }
+
+    protected void updateSearchResultsVisibility() {
+        if (isSearching()) {
+            getSearchRecyclerView().setVisibility(VISIBLE);
+            getAppsRecyclerViewContainer().setVisibility(GONE);
+            mHeader.setVisibility(GONE);
+        } else {
+            getSearchRecyclerView().setVisibility(GONE);
+            getAppsRecyclerViewContainer().setVisibility(VISIBLE);
+            mHeader.setVisibility(VISIBLE);
+        }
+        if (mHeader.isSetUp()) {
+            mHeader.setActiveRV(getCurrentPage());
+        }
+    }
+
+    private void applyAdapterSideAndBottomPaddings(DeviceProfile grid) {
+        int bottomPadding = Math.max(mInsets.bottom, mNavBarScrimHeight);
+        mAH.forEach(adapterHolder -> {
+            adapterHolder.mPadding.bottom = bottomPadding;
+            adapterHolder.mPadding.left =
+                    adapterHolder.mPadding.right = grid.allAppsLeftRightPadding;
+            adapterHolder.applyPadding();
+        });
+    }
+
+    private void setDeviceManagementResources() {
+        if (mActivityContext.getStringCache() != null) {
+            Button personalTab = findViewById(R.id.tab_personal);
+            personalTab.setText(mActivityContext.getStringCache().allAppsPersonalTab);
+
+            Button workTab = findViewById(R.id.tab_work);
+            workTab.setText(mActivityContext.getStringCache().allAppsWorkTab);
+        }
+    }
+
+    protected boolean shouldShowTabs() {
+        return mHasWorkApps;
+    }
+
+    // Used by tests only
+    private boolean isDescendantViewVisible(int viewId) {
+        final View view = findViewById(viewId);
+        if (view == null) return false;
+
+        if (!view.isShown()) return false;
+
+        return view.getGlobalVisibleRect(new Rect());
+    }
+
+    @VisibleForTesting
+    public boolean isPersonalTabVisible() {
+        return isDescendantViewVisible(R.id.tab_personal);
+    }
+
+    @VisibleForTesting
+    public boolean isWorkTabVisible() {
+        return isDescendantViewVisible(R.id.tab_work);
+    }
+
+    public AlphabeticalAppsList<T> getSearchResultList() {
+        return mAH.get(SEARCH).mAppsList;
+    }
+
+    public FloatingHeaderView getFloatingHeaderView() {
+        return mHeader;
+    }
+
+    @VisibleForTesting
+    public View getContentView() {
+        return isSearching() ? getSearchRecyclerView() : getAppsRecyclerViewContainer();
+    }
+
+    /** The current page visible in all apps. */
+    public int getCurrentPage() {
+        return isSearching()
+                ? SEARCH
+                : mViewPager == null ? AdapterHolder.MAIN : mViewPager.getNextPage();
+    }
+
+    /** The scroll bar for the active apps recycler view. */
+    public RecyclerViewFastScroller getScrollBar() {
+        AllAppsRecyclerView rv = getActiveAppsRecyclerView();
+        return rv == null ? null : rv.getScrollbar();
+    }
+
+    public boolean isHeaderVisible() {
+        return mHeader != null && mHeader.getVisibility() == View.VISIBLE;
+    }
+
+    /**
+     * Adds an update listener to animator that adds springs to the animation.
+     */
+    public void addSpringFromFlingUpdateListener(ValueAnimator animator,
+            float velocity /* release velocity */,
+            float progress /* portion of the distance to travel*/) {
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animator) {
+                float distance = (1 - progress) * getHeight(); // px
+                float settleVelocity = Math.min(0, distance
+                        / (AllAppsTransitionController.INTERP_COEFF * animator.getDuration())
+                        + velocity);
+                absorbSwipeUpVelocity(Math.max(1000, Math.abs(
+                        Math.round(settleVelocity * FLING_VELOCITY_MULTIPLIER))));
+            }
+        });
+    }
+
+    /** Invoked when the container is pulled. */
+    public void onPull(float deltaDistance, float displacement) {
+        absorbPullDeltaDistance(PULL_MULTIPLIER * deltaDistance, PULL_MULTIPLIER * displacement);
+        // Current motion spec is to actually push and not pull
+        // on this surface. However, until EdgeEffect.onPush (b/190612804) is
+        // implemented at view level, we will simply pull
+    }
+
+    @Override
+    public void getDrawingRect(Rect outRect) {
+        super.getDrawingRect(outRect);
+        outRect.offset(0, (int) getTranslationY());
+    }
+
+    @Override
+    public void setTranslationY(float translationY) {
+        super.setTranslationY(translationY);
+        invalidateHeader();
+    }
+
+    public void setScrimView(ScrimView scrimView) {
+        mScrimView = scrimView;
+    }
+
+    @Override
+    public void drawOnScrimWithScale(Canvas canvas, float scale) {
+        boolean isTablet = mActivityContext.getDeviceProfile().isTablet;
+
+        // Draw full background panel for tablets.
+        if (isTablet) {
+            mHeaderPaint.setColor(mBottomSheetBackgroundColor);
+            View panel = (View) mBottomSheetBackground;
+            float translationY = ((View) panel.getParent()).getTranslationY();
+            mTmpRectF.set(panel.getLeft(), panel.getTop() + translationY,
+                    panel.getRight(), panel.getBottom());
+            mTmpPath.reset();
+            mTmpPath.addRoundRect(mTmpRectF, mBottomSheetCornerRadii, Direction.CW);
+            canvas.drawPath(mTmpPath, mHeaderPaint);
+        }
+
+        if (DEBUG_HEADER_PROTECTION) {
+            mHeaderPaint.setColor(Color.MAGENTA);
+            mHeaderPaint.setAlpha(255);
+        } else {
+            mHeaderPaint.setColor(mHeaderColor);
+            mHeaderPaint.setAlpha((int) (getAlpha() * Color.alpha(mHeaderColor)));
+        }
+        if (mHeaderPaint.getColor() == mScrimColor || mHeaderPaint.getColor() == 0) {
+            return;
+        }
+        final float offset = (getVisibleContainerView().getHeight() * (1 - scale) / 2);
+        final float bottom =
+                scale * (getHeaderBottom() + getVisibleContainerView().getPaddingTop()) + offset;
+        FloatingHeaderView headerView = getFloatingHeaderView();
+        if (isTablet) {
+            // Start adding header protection if search bar or tabs will attach to the top.
+            if (!FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get() || mUsingTabs) {
+                View panel = (View) mBottomSheetBackground;
+                float translationY = ((View) panel.getParent()).getTranslationY();
+                mTmpRectF.set(panel.getLeft(), panel.getTop() + translationY, panel.getRight(),
+                        bottom);
+                mTmpPath.reset();
+                mTmpPath.addRoundRect(mTmpRectF, mBottomSheetCornerRadii, Direction.CW);
+                canvas.drawPath(mTmpPath, mHeaderPaint);
+            }
+        } else {
+            canvas.drawRect(0, 0, canvas.getWidth(), bottom, mHeaderPaint);
+        }
+        int tabsHeight = headerView.getPeripheralProtectionHeight();
+        if (mTabsProtectionAlpha > 0 && tabsHeight != 0) {
+            if (DEBUG_HEADER_PROTECTION) {
+                mHeaderPaint.setColor(Color.BLUE);
+                mHeaderPaint.setAlpha(255);
+            } else {
+                mHeaderPaint.setAlpha((int) (getAlpha() * mTabsProtectionAlpha));
+            }
+            int left = 0;
+            int right = canvas.getWidth();
+            if (isTablet) {
+                left = mBottomSheetBackground.getLeft();
+                right = mBottomSheetBackground.getRight();
+            }
+            canvas.drawRect(left, bottom, right, bottom + tabsHeight, mHeaderPaint);
+        }
+    }
+
+    /**
+     * redraws header protection
+     */
+    public void invalidateHeader() {
+        if (mScrimView != null) {
+            mScrimView.invalidate();
+        }
+    }
+
+    /** Returns the position of the bottom edge of the header */
+    public int getHeaderBottom() {
+        int bottom = (int) getTranslationY() + mHeader.getClipTop();
+        if (FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get()) {
+            if (mActivityContext.getDeviceProfile().isTablet) {
+                return bottom + mBottomSheetBackground.getTop();
+            }
+            return bottom;
+        }
+        return bottom + mHeader.getTop();
+    }
+
+    /**
+     * Returns a view that denotes the visible part of all apps container view.
+     */
+    public View getVisibleContainerView() {
+        return mActivityContext.getDeviceProfile().isTablet ? mBottomSheetBackground : this;
+    }
+
+    protected void onInitializeRecyclerView(RecyclerView rv) {
+        rv.addOnScrollListener(mScrollListener);
+    }
+
+    /** Holds a {@link BaseAllAppsAdapter} and related fields. */
+    public class AdapterHolder {
+        public static final int MAIN = 0;
+        public static final int WORK = 1;
+        public static final int SEARCH = 2;
+
+        private final int mType;
+        public final BaseAllAppsAdapter<T> mAdapter;
+        final RecyclerView.LayoutManager mLayoutManager;
+        final AlphabeticalAppsList<T> mAppsList;
+        final Rect mPadding = new Rect();
+        AllAppsRecyclerView mRecyclerView;
+
+        AdapterHolder(int type) {
+            mType = type;
+            mAppsList = new AlphabeticalAppsList<>(mActivityContext,
+                    isSearch() ? null : mAllAppsStore,
+                    isWork() ? mWorkManager : null);
+            BaseAdapterProvider[] adapterProviders =
+                    new BaseAdapterProvider[]{mMainAdapterProvider};
+
+            mAdapter = createAdapter(mAppsList, adapterProviders);
+            mAppsList.setAdapter(mAdapter);
+            mLayoutManager = mAdapter.getLayoutManager();
+        }
+
+        void setup(@NonNull View rv, @Nullable Predicate<ItemInfo> matcher) {
+            mAppsList.updateItemFilter(matcher);
+            mRecyclerView = (AllAppsRecyclerView) rv;
+            mRecyclerView.setEdgeEffectFactory(createEdgeEffectFactory());
+            mRecyclerView.setApps(mAppsList);
+            mRecyclerView.setLayoutManager(mLayoutManager);
+            mRecyclerView.setAdapter(mAdapter);
+            mRecyclerView.setHasFixedSize(true);
+            // No animations will occur when changes occur to the items in this RecyclerView.
+            mRecyclerView.setItemAnimator(null);
+            onInitializeRecyclerView(mRecyclerView);
+            FocusedItemDecorator focusedItemDecorator = new FocusedItemDecorator(mRecyclerView);
+            mRecyclerView.addItemDecoration(focusedItemDecorator);
+            mAdapter.setIconFocusListener(focusedItemDecorator.getFocusListener());
+            applyPadding();
+        }
+
+        void applyPadding() {
+            if (mRecyclerView != null) {
+                int bottomOffset = 0;
+                if (isWork() && mWorkManager.getWorkModeSwitch() != null) {
+                    bottomOffset = mInsets.bottom + mWorkManager.getWorkModeSwitch().getHeight();
+                }
+                mRecyclerView.setPadding(mPadding.left, mPadding.top, mPadding.right,
+                        mPadding.bottom + bottomOffset);
+            }
+        }
+
+        private boolean isWork() {
+            return mType == WORK;
+        }
+
+        private boolean isSearch() {
+            return mType == SEARCH;
+        }
+    }
 }
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 8cb31fa..7789191 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -299,7 +299,7 @@
             return;
         } else if (appsView.mViewPager != null) {
             int currentPage = appsView.mViewPager.getCurrentPage();
-            if (currentPage == BaseAllAppsContainerView.AdapterHolder.WORK) {
+            if (currentPage == ActivityAllAppsContainerView.AdapterHolder.WORK) {
                 // In work A-Z list
                 mgr.logger().withContainerInfo(containerInfo).log((mCumulativeVerticalScroll > 0)
                         ? LAUNCHER_WORK_FAB_BUTTON_COLLAPSE
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
deleted file mode 100644
index 9bb8250..0000000
--- a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
+++ /dev/null
@@ -1,996 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.allapps;
-
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_COUNT;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_TAP_ON_PERSONAL_TAB;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_TAP_ON_WORK_TAB;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.Path.Direction;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.os.Bundle;
-import android.os.Parcelable;
-import android.os.Process;
-import android.os.UserManager;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.SparseArray;
-import android.util.TypedValue;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.WindowInsets;
-import android.widget.Button;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-import androidx.core.graphics.ColorUtils;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
-import com.android.launcher3.DragSource;
-import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.Insettable;
-import com.android.launcher3.InsettableFrameLayout;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.allapps.search.DefaultSearchAdapterProvider;
-import com.android.launcher3.allapps.search.SearchAdapterProvider;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.keyboard.FocusedItemDecorator;
-import com.android.launcher3.model.StringCache;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.util.ItemInfoMatcher;
-import com.android.launcher3.util.Themes;
-import com.android.launcher3.views.ActivityContext;
-import com.android.launcher3.views.BaseDragLayer;
-import com.android.launcher3.views.RecyclerViewFastScroller;
-import com.android.launcher3.views.ScrimView;
-import com.android.launcher3.views.SpringRelativeLayout;
-import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePageChangedListener;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.function.Predicate;
-import java.util.stream.Stream;
-
-/**
- * Base all apps view container.
- *
- * @param <T> Type of context inflating all apps.
- */
-public abstract class BaseAllAppsContainerView<T extends Context & ActivityContext>
-        extends SpringRelativeLayout implements DragSource, Insettable,
-        OnDeviceProfileChangeListener, OnActivePageChangedListener,
-        ScrimView.ScrimDrawingController {
-
-    protected static final String BUNDLE_KEY_CURRENT_PAGE = "launcher.allapps.current_page";
-
-    public static final float PULL_MULTIPLIER = .02f;
-    public static final float FLING_VELOCITY_MULTIPLIER = 1200f;
-
-    // Render the header protection at all times to debug clipping issues.
-    private static final boolean DEBUG_HEADER_PROTECTION = false;
-
-    private final Paint mHeaderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-    private final Rect mInsets = new Rect();
-
-    /** Context of an activity or window that is inflating this container. */
-    protected final T mActivityContext;
-    protected final List<AdapterHolder> mAH;
-    protected final Predicate<ItemInfo> mPersonalMatcher = ItemInfoMatcher.ofUser(
-            Process.myUserHandle());
-    private final AllAppsStore mAllAppsStore = new AllAppsStore();
-
-    private final RecyclerView.OnScrollListener mScrollListener =
-            new RecyclerView.OnScrollListener() {
-                @Override
-                public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
-                    updateHeaderScroll(recyclerView.computeVerticalScrollOffset());
-                }
-            };
-
-    protected final WorkProfileManager mWorkManager;
-
-    private final Paint mNavBarScrimPaint;
-    private int mNavBarScrimHeight = 0;
-
-    protected AllAppsPagedView mViewPager;
-    private SearchRecyclerView mSearchRecyclerView;
-    private SearchAdapterProvider<?> mMainAdapterProvider;
-
-    protected FloatingHeaderView mHeader;
-    protected View mBottomSheetBackground;
-    private View mBottomSheetHandleArea;
-
-    /**
-     * View that defines the search box. Result is rendered inside {@link #mSearchRecyclerView}.
-     */
-    protected View mSearchContainer;
-    protected SearchUiManager mSearchUiManager;
-
-    protected boolean mUsingTabs;
-    private boolean mHasWorkApps;
-
-    protected RecyclerViewFastScroller mTouchHandler;
-    protected final Point mFastScrollerOffset = new Point();
-
-    protected final int mScrimColor;
-    private final int mHeaderProtectionColor;
-    protected final float mHeaderThreshold;
-    private final Path mTmpPath = new Path();
-    private final RectF mTmpRectF = new RectF();
-    private float[] mBottomSheetCornerRadii;
-    private ScrimView mScrimView;
-    private int mHeaderColor;
-    private int mBottomSheetBackgroundColor;
-    private int mTabsProtectionAlpha;
-
-    protected BaseAllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-        mActivityContext = ActivityContext.lookupContext(context);
-
-        mScrimColor = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
-        mHeaderThreshold = getResources().getDimensionPixelSize(
-                R.dimen.dynamic_grid_cell_border_spacing);
-        mHeaderProtectionColor = Themes.getAttrColor(context, R.attr.allappsHeaderProtectionColor);
-
-        mWorkManager = new WorkProfileManager(mActivityContext.getSystemService(UserManager.class),
-                this, mActivityContext.getStatsLogManager());
-        mAH = Arrays.asList(null, null, null);
-        mNavBarScrimPaint = new Paint();
-        mNavBarScrimPaint.setColor(Themes.getAttrColor(context, R.attr.allAppsNavBarScrimColor));
-
-        mAllAppsStore.addUpdateListener(this::onAppsUpdated);
-        mActivityContext.addOnDeviceProfileChangeListener(this);
-
-        // This is a focus listener that proxies focus from a view into the list view.  This is to
-        // work around the search box from getting first focus and showing the cursor.
-        setOnFocusChangeListener((v, hasFocus) -> {
-            if (hasFocus && getActiveRecyclerView() != null) {
-                getActiveRecyclerView().requestFocus();
-            }
-        });
-        initContent();
-    }
-
-    /**
-     * Initializes the view hierarchy and internal variables. Any initialization which actually uses
-     * these members should be done in {@link #onFinishInflate()}.
-     * In terms of subclass initialization, the following would be parallel order for activity:
-     *   initContent -> onPreCreate
-     *   constructor/init -> onCreate
-     *   onFinishInflate -> onPostCreate
-     */
-    protected void initContent() {
-        mMainAdapterProvider = createMainAdapterProvider();
-
-        mAH.set(AdapterHolder.MAIN, new AdapterHolder(AdapterHolder.MAIN));
-        mAH.set(AdapterHolder.WORK, new AdapterHolder(AdapterHolder.WORK));
-        mAH.set(AdapterHolder.SEARCH, new AdapterHolder(AdapterHolder.SEARCH));
-
-        getLayoutInflater().inflate(R.layout.all_apps_content, this);
-        mHeader = findViewById(R.id.all_apps_header);
-        mBottomSheetBackground = findViewById(R.id.bottom_sheet_background);
-        mBottomSheetHandleArea = findViewById(R.id.bottom_sheet_handle_area);
-        mSearchRecyclerView = findViewById(R.id.search_results_list_view);
-
-        // Add the search box next to the header
-        mSearchContainer = inflateSearchBox();
-        addView(mSearchContainer, indexOfChild(mHeader) + 1);
-        mSearchUiManager = (SearchUiManager) mSearchContainer;
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-
-        mAH.get(AdapterHolder.SEARCH).setup(mSearchRecyclerView,
-                /* Filter out A-Z apps */ itemInfo -> false);
-        rebindAdapters(true /* force */);
-        float cornerRadius = Themes.getDialogCornerRadius(getContext());
-        mBottomSheetCornerRadii = new float[]{
-                cornerRadius,
-                cornerRadius, // Top left radius in px
-                cornerRadius,
-                cornerRadius, // Top right radius in px
-                0,
-                0, // Bottom right
-                0,
-                0 // Bottom left
-        };
-        final TypedValue value = new TypedValue();
-        getContext().getTheme().resolveAttribute(android.R.attr.colorBackground, value, true);
-        mBottomSheetBackgroundColor = value.data;
-        updateBackground(mActivityContext.getDeviceProfile());
-    }
-
-    /**
-     * Inflates the search box
-     */
-    protected View inflateSearchBox() {
-        return getLayoutInflater().inflate(R.layout.search_container_all_apps, this, false);
-    }
-
-    /** Creates the adapter provider for the main section. */
-    protected SearchAdapterProvider<?> createMainAdapterProvider() {
-        return new DefaultSearchAdapterProvider(mActivityContext);
-    }
-
-    /** The adapter provider for the main section. */
-    public final SearchAdapterProvider<?> getMainAdapterProvider() {
-        return mMainAdapterProvider;
-    }
-
-    @Override
-    protected void dispatchRestoreInstanceState(SparseArray<Parcelable> sparseArray) {
-        try {
-            // Many slice view id is not properly assigned, and hence throws null
-            // pointer exception in the underneath method. Catching the exception
-            // simply doesn't restore these slice views. This doesn't have any
-            // user visible effect because because we query them again.
-            super.dispatchRestoreInstanceState(sparseArray);
-        } catch (Exception e) {
-            Log.e("AllAppsContainerView", "restoreInstanceState viewId = 0", e);
-        }
-
-        Bundle state = (Bundle) sparseArray.get(R.id.work_tab_state_id, null);
-        if (state != null) {
-            int currentPage = state.getInt(BUNDLE_KEY_CURRENT_PAGE, 0);
-            if (currentPage == AdapterHolder.WORK && mViewPager != null) {
-                mViewPager.setCurrentPage(currentPage);
-                rebindAdapters();
-            } else {
-                reset(true);
-            }
-        }
-    }
-
-    @Override
-    protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
-        super.dispatchSaveInstanceState(container);
-        Bundle state = new Bundle();
-        state.putInt(BUNDLE_KEY_CURRENT_PAGE, getCurrentPage());
-        container.put(R.id.work_tab_state_id, state);
-    }
-
-    /**
-     * Sets the long click listener for icons
-     */
-    public void setOnIconLongClickListener(OnLongClickListener listener) {
-        for (AdapterHolder holder : mAH) {
-            holder.mAdapter.setOnIconLongClickListener(listener);
-        }
-    }
-
-    public AllAppsStore getAppsStore() {
-        return mAllAppsStore;
-    }
-
-    public WorkProfileManager getWorkManager() {
-        return mWorkManager;
-    }
-
-    @Override
-    public void onDeviceProfileChanged(DeviceProfile dp) {
-        for (AdapterHolder holder : mAH) {
-            holder.mAdapter.setAppsPerRow(dp.numShownAllAppsColumns);
-            if (holder.mRecyclerView != null) {
-                // Remove all views and clear the pool, while keeping the data same. After this
-                // call, all the viewHolders will be recreated.
-                holder.mRecyclerView.swapAdapter(holder.mRecyclerView.getAdapter(), true);
-                holder.mRecyclerView.getRecycledViewPool().clear();
-            }
-        }
-        updateBackground(dp);
-    }
-
-    protected void updateBackground(DeviceProfile deviceProfile) {
-        mBottomSheetBackground.setVisibility(deviceProfile.isTablet ? View.VISIBLE : View.GONE);
-        // Note: For tablets, the opaque background and header protection are added in drawOnScrim.
-        // For the taskbar entrypoint, the scrim is drawn differently, so a static background is
-        // added in TaskbarAllAppsContainerView and header protection is not yet supported.
-    }
-
-    private void onAppsUpdated() {
-        mHasWorkApps = Stream.of(mAllAppsStore.getApps()).anyMatch(mWorkManager.getMatcher());
-        if (!isSearching()) {
-            rebindAdapters();
-            if (mHasWorkApps) {
-                mWorkManager.reset();
-            }
-        }
-
-        mActivityContext.getStatsLogManager().logger()
-                .withCardinality(mAllAppsStore.getApps().length)
-                .log(LAUNCHER_ALLAPPS_COUNT);
-    }
-
-    /**
-     * Returns whether the view itself will handle the touch event or not.
-     */
-    public boolean shouldContainerScroll(MotionEvent ev) {
-        BaseDragLayer dragLayer = mActivityContext.getDragLayer();
-        // Scroll if not within the container view (e.g. over large-screen scrim).
-        if (!dragLayer.isEventOverView(getVisibleContainerView(), ev)) {
-            return true;
-        }
-        if (dragLayer.isEventOverView(mBottomSheetHandleArea, ev)) {
-            return true;
-        }
-        AllAppsRecyclerView rv = getActiveRecyclerView();
-        if (rv == null) {
-            return true;
-        }
-        if (rv.getScrollbar() != null
-                && rv.getScrollbar().getThumbOffsetY() >= 0
-                && dragLayer.isEventOverView(rv.getScrollbar(), ev)) {
-            return false;
-        }
-        return rv.shouldContainerScroll(ev, dragLayer);
-    }
-
-    @Override
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
-        // The AllAppsContainerView houses the QSB and is hence visible from the Workspace
-        // Overview states. We shouldn't intercept for the scrubber in these cases.
-        if (!isInAllApps()) {
-            mTouchHandler = null;
-            return false;
-        }
-
-        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            AllAppsRecyclerView rv = getActiveRecyclerView();
-            if (rv != null && rv.getScrollbar() != null
-                    && rv.getScrollbar().isHitInParent(ev.getX(), ev.getY(), mFastScrollerOffset)) {
-                mTouchHandler = rv.getScrollbar();
-            } else {
-                mTouchHandler = null;
-            }
-        }
-        if (mTouchHandler != null) {
-            return mTouchHandler.handleTouchEvent(ev, mFastScrollerOffset);
-        }
-        return false;
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent ev) {
-        if (!isInAllApps()) {
-            return false;
-        }
-
-        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            AllAppsRecyclerView rv = getActiveRecyclerView();
-            if (rv != null && rv.getScrollbar() != null
-                    && rv.getScrollbar().isHitInParent(ev.getX(), ev.getY(), mFastScrollerOffset)) {
-                mTouchHandler = rv.getScrollbar();
-            } else {
-                mTouchHandler = null;
-
-            }
-        }
-        if (mTouchHandler != null) {
-            mTouchHandler.handleTouchEvent(ev, mFastScrollerOffset);
-            return true;
-        }
-        if (isSearching()
-                && mActivityContext.getDragLayer().isEventOverView(getVisibleContainerView(), ev)) {
-            // if in search state, consume touch event.
-            return true;
-        }
-        return false;
-    }
-
-    /** Description of the container view based on its current state. */
-    public String getDescription() {
-        StringCache cache = mActivityContext.getStringCache();
-        if (mUsingTabs) {
-            if (cache != null) {
-                return isPersonalTab()
-                        ? cache.allAppsPersonalTabAccessibility
-                        : cache.allAppsWorkTabAccessibility;
-            } else {
-                return isPersonalTab()
-                        ? getContext().getString(R.string.all_apps_button_personal_label)
-                        : getContext().getString(R.string.all_apps_button_work_label);
-            }
-        }
-        return getContext().getString(R.string.all_apps_button_label);
-    }
-
-    /** The current active recycler view (A-Z list from one of the profiles, or search results). */
-    public AllAppsRecyclerView getActiveRecyclerView() {
-        if (isSearching()) {
-            return getSearchRecyclerView();
-        }
-        return getActiveAppsRecyclerView();
-    }
-
-    /** The current apps recycler view in the container. */
-    private AllAppsRecyclerView getActiveAppsRecyclerView() {
-        if (!mUsingTabs || isPersonalTab()) {
-            return mAH.get(AdapterHolder.MAIN).mRecyclerView;
-        } else {
-            return mAH.get(AdapterHolder.WORK).mRecyclerView;
-        }
-    }
-
-    /**
-     * The container for A-Z apps (the ViewPager for main+work tabs, or main RV). This is currently
-     * hidden while searching.
-     **/
-    protected View getAppsRecyclerViewContainer() {
-        return mViewPager != null ? mViewPager : findViewById(R.id.apps_list_view);
-    }
-
-    /** The RV for search results, which is hidden while A-Z apps are visible. */
-    public SearchRecyclerView getSearchRecyclerView() {
-        return mSearchRecyclerView;
-    }
-
-    protected boolean isPersonalTab() {
-        return mViewPager == null || mViewPager.getNextPage() == 0;
-    }
-
-    /**
-     * Switches the current page to the provided {@code tab} if tabs are supported, otherwise does
-     * nothing.
-     */
-    public void switchToTab(int tab) {
-        if (mUsingTabs) {
-            mViewPager.setCurrentPage(tab);
-        }
-    }
-
-    public LayoutInflater getLayoutInflater() {
-        return LayoutInflater.from(getContext());
-    }
-
-    /**
-     * Resets the state of AllApps.
-     */
-    public void reset(boolean animate) {
-        for (int i = 0; i < mAH.size(); i++) {
-            if (mAH.get(i).mRecyclerView != null) {
-                mAH.get(i).mRecyclerView.scrollToTop();
-            }
-        }
-        if (isHeaderVisible()) {
-            mHeader.reset(animate);
-        }
-        // Reset the base recycler view after transitioning home.
-        updateHeaderScroll(0);
-    }
-
-    @Override
-    public void onDropCompleted(View target, DragObject d, boolean success) {}
-
-    @Override
-    public void setInsets(Rect insets) {
-        mInsets.set(insets);
-        DeviceProfile grid = mActivityContext.getDeviceProfile();
-
-        applyAdapterSideAndBottomPaddings(grid);
-
-        MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
-        mlp.leftMargin = insets.left;
-        mlp.rightMargin = insets.right;
-        setLayoutParams(mlp);
-
-        if (grid.isVerticalBarLayout()) {
-            setPadding(grid.workspacePadding.left, 0, grid.workspacePadding.right, 0);
-        } else {
-            int topPadding = grid.allAppsTopPadding;
-            if (FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get() && !grid.isTablet) {
-                topPadding += getResources().getDimensionPixelSize(
-                        R.dimen.all_apps_additional_top_padding_floating_search);
-            }
-            setPadding(grid.allAppsLeftRightMargin, topPadding, grid.allAppsLeftRightMargin, 0);
-        }
-
-        InsettableFrameLayout.dispatchInsets(this, insets);
-    }
-
-    /**
-     * Returns a padding in case a scrim is shown on the bottom of the view and a padding is needed.
-     */
-    protected int computeNavBarScrimHeight(WindowInsets insets) {
-        return 0;
-    }
-
-    /**
-     * Returns the current height of nav bar scrim
-     */
-    public int getNavBarScrimHeight() {
-        return mNavBarScrimHeight;
-    }
-
-    @Override
-    public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
-        mNavBarScrimHeight = computeNavBarScrimHeight(insets);
-        applyAdapterSideAndBottomPaddings(mActivityContext.getDeviceProfile());
-        return super.dispatchApplyWindowInsets(insets);
-    }
-
-    @Override
-    protected void dispatchDraw(Canvas canvas) {
-        super.dispatchDraw(canvas);
-
-        if (mNavBarScrimHeight > 0) {
-            canvas.drawRect(0, getHeight() - mNavBarScrimHeight, getWidth(), getHeight(),
-                    mNavBarScrimPaint);
-        }
-    }
-
-    protected void rebindAdapters() {
-        rebindAdapters(false /* force */);
-    }
-
-    protected void rebindAdapters(boolean force) {
-        updateSearchResultsVisibility();
-
-        boolean showTabs = shouldShowTabs();
-        if (showTabs == mUsingTabs && !force) {
-            return;
-        }
-
-        if (isSearching()) {
-            mUsingTabs = showTabs;
-            mWorkManager.detachWorkModeSwitch();
-            return;
-        }
-
-        // replaceAppsRVcontainer() needs to use both mUsingTabs value to remove the old view AND
-        // showTabs value to create new view. Hence the mUsingTabs new value assignment MUST happen
-        // after this call.
-        replaceAppsRVContainer(showTabs);
-        mUsingTabs = showTabs;
-
-        mAllAppsStore.unregisterIconContainer(mAH.get(AdapterHolder.MAIN).mRecyclerView);
-        mAllAppsStore.unregisterIconContainer(mAH.get(AdapterHolder.WORK).mRecyclerView);
-        mAllAppsStore.unregisterIconContainer(mAH.get(AdapterHolder.SEARCH).mRecyclerView);
-
-        if (mUsingTabs) {
-            mAH.get(AdapterHolder.MAIN).setup(mViewPager.getChildAt(0), mPersonalMatcher);
-            mAH.get(AdapterHolder.WORK).setup(mViewPager.getChildAt(1), mWorkManager.getMatcher());
-            mAH.get(AdapterHolder.WORK).mRecyclerView.setId(R.id.apps_list_view_work);
-            if (FeatureFlags.ENABLE_EXPANDING_PAUSE_WORK_BUTTON.get()) {
-                mAH.get(AdapterHolder.WORK).mRecyclerView.addOnScrollListener(
-                        mWorkManager.newScrollListener());
-            }
-            mViewPager.getPageIndicator().setActiveMarker(AdapterHolder.MAIN);
-            findViewById(R.id.tab_personal)
-                    .setOnClickListener((View view) -> {
-                        if (mViewPager.snapToPage(AdapterHolder.MAIN)) {
-                            mActivityContext.getStatsLogManager().logger()
-                                    .log(LAUNCHER_ALLAPPS_TAP_ON_PERSONAL_TAB);
-                        }
-                        mActivityContext.hideKeyboard();
-                    });
-            findViewById(R.id.tab_work)
-                    .setOnClickListener((View view) -> {
-                        if (mViewPager.snapToPage(AdapterHolder.WORK)) {
-                            mActivityContext.getStatsLogManager().logger()
-                                    .log(LAUNCHER_ALLAPPS_TAP_ON_WORK_TAB);
-                        }
-                        mActivityContext.hideKeyboard();
-                    });
-            setDeviceManagementResources();
-            onActivePageChanged(mViewPager.getNextPage());
-        } else {
-            mAH.get(AdapterHolder.MAIN).setup(findViewById(R.id.apps_list_view), null);
-            mAH.get(AdapterHolder.WORK).mRecyclerView = null;
-        }
-        setupHeader();
-
-        mAllAppsStore.registerIconContainer(mAH.get(AdapterHolder.MAIN).mRecyclerView);
-        mAllAppsStore.registerIconContainer(mAH.get(AdapterHolder.WORK).mRecyclerView);
-        mAllAppsStore.registerIconContainer(mAH.get(AdapterHolder.SEARCH).mRecyclerView);
-    }
-
-    protected void updateSearchResultsVisibility() {
-        if (isSearching()) {
-            getSearchRecyclerView().setVisibility(VISIBLE);
-            getAppsRecyclerViewContainer().setVisibility(GONE);
-            mHeader.setVisibility(GONE);
-        } else {
-            getSearchRecyclerView().setVisibility(GONE);
-            getAppsRecyclerViewContainer().setVisibility(VISIBLE);
-            mHeader.setVisibility(VISIBLE);
-        }
-        if (mHeader.isSetUp()) {
-            mHeader.setActiveRV(getCurrentPage());
-        }
-    }
-
-    private void applyAdapterSideAndBottomPaddings(DeviceProfile grid) {
-        int bottomPadding = Math.max(mInsets.bottom, mNavBarScrimHeight);
-        mAH.forEach(adapterHolder -> {
-            adapterHolder.mPadding.bottom = bottomPadding;
-            adapterHolder.mPadding.left =
-                    adapterHolder.mPadding.right = grid.allAppsLeftRightPadding;
-            adapterHolder.applyPadding();
-        });
-    }
-
-    private void setDeviceManagementResources() {
-        if (mActivityContext.getStringCache() != null) {
-            Button personalTab = findViewById(R.id.tab_personal);
-            personalTab.setText(mActivityContext.getStringCache().allAppsPersonalTab);
-
-            Button workTab = findViewById(R.id.tab_work);
-            workTab.setText(mActivityContext.getStringCache().allAppsWorkTab);
-        }
-    }
-
-    protected boolean shouldShowTabs() {
-        return mHasWorkApps;
-    }
-
-    protected boolean isSearching() {
-        return false;
-    }
-
-    protected View replaceAppsRVContainer(boolean showTabs) {
-        for (int i = AdapterHolder.MAIN; i <= AdapterHolder.WORK; i++) {
-            AdapterHolder adapterHolder = mAH.get(i);
-            if (adapterHolder.mRecyclerView != null) {
-                adapterHolder.mRecyclerView.setLayoutManager(null);
-                adapterHolder.mRecyclerView.setAdapter(null);
-            }
-        }
-        View oldView = getAppsRecyclerViewContainer();
-        int index = indexOfChild(oldView);
-        removeView(oldView);
-        int layout = showTabs ? R.layout.all_apps_tabs : R.layout.all_apps_rv_layout;
-        View newView = getLayoutInflater().inflate(layout, this, false);
-        addView(newView, index);
-        if (showTabs) {
-            mViewPager = (AllAppsPagedView) newView;
-            mViewPager.initParentViews(this);
-            mViewPager.getPageIndicator().setOnActivePageChangedListener(this);
-
-            mWorkManager.reset();
-            post(() -> mAH.get(AdapterHolder.WORK).applyPadding());
-
-        } else {
-            mWorkManager.detachWorkModeSwitch();
-            mViewPager = null;
-        }
-        return newView;
-    }
-
-    @Override
-    public void onActivePageChanged(int currentActivePage) {
-        if (mAH.get(currentActivePage).mRecyclerView != null) {
-            mAH.get(currentActivePage).mRecyclerView.bindFastScrollbar();
-        }
-        // Header keeps track of active recycler view to properly render header protection.
-        mHeader.setActiveRV(currentActivePage);
-        reset(true /* animate */);
-
-        mWorkManager.onActivePageChanged(currentActivePage);
-    }
-
-    // Used by tests only
-    private boolean isDescendantViewVisible(int viewId) {
-        final View view = findViewById(viewId);
-        if (view == null) return false;
-
-        if (!view.isShown()) return false;
-
-        return view.getGlobalVisibleRect(new Rect());
-    }
-
-    @VisibleForTesting
-    public boolean isPersonalTabVisible() {
-        return isDescendantViewVisible(R.id.tab_personal);
-    }
-
-    @VisibleForTesting
-    public boolean isWorkTabVisible() {
-        return isDescendantViewVisible(R.id.tab_work);
-    }
-
-    public AlphabeticalAppsList<T> getSearchResultList() {
-        return mAH.get(AdapterHolder.SEARCH).mAppsList;
-    }
-
-    public FloatingHeaderView getFloatingHeaderView() {
-        return mHeader;
-    }
-
-    @VisibleForTesting
-    public View getContentView() {
-        return isSearching() ? getSearchRecyclerView() : getAppsRecyclerViewContainer();
-    }
-
-    /** The current page visible in all apps. */
-    public int getCurrentPage() {
-        return isSearching()
-                ? AdapterHolder.SEARCH
-                : mViewPager == null ? AdapterHolder.MAIN : mViewPager.getNextPage();
-    }
-
-    /** The scroll bar for the active apps recycler view. */
-    public RecyclerViewFastScroller getScrollBar() {
-        AllAppsRecyclerView rv = getActiveAppsRecyclerView();
-        return rv == null ? null : rv.getScrollbar();
-    }
-
-    void setupHeader() {
-        mHeader.setVisibility(View.VISIBLE);
-        boolean tabsHidden = !mUsingTabs;
-        mHeader.setup(
-                mAH.get(AdapterHolder.MAIN).mRecyclerView,
-                mAH.get(AdapterHolder.WORK).mRecyclerView,
-                (SearchRecyclerView) mAH.get(AdapterHolder.SEARCH).mRecyclerView,
-                getCurrentPage(),
-                tabsHidden);
-
-        int padding = mHeader.getMaxTranslation();
-        mAH.forEach(adapterHolder -> {
-            adapterHolder.mPadding.top = padding;
-            adapterHolder.applyPadding();
-            if (adapterHolder.mRecyclerView != null) {
-                adapterHolder.mRecyclerView.scrollToTop();
-            }
-        });
-    }
-
-    public boolean isHeaderVisible() {
-        return mHeader != null && mHeader.getVisibility() == View.VISIBLE;
-    }
-
-    /**
-     * Adds an update listener to animator that adds springs to the animation.
-     */
-    public void addSpringFromFlingUpdateListener(ValueAnimator animator,
-            float velocity /* release velocity */,
-            float progress /* portion of the distance to travel*/) {
-        animator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationStart(Animator animator) {
-                float distance = (1 - progress) * getHeight(); // px
-                float settleVelocity = Math.min(0, distance
-                        / (AllAppsTransitionController.INTERP_COEFF * animator.getDuration())
-                        + velocity);
-                absorbSwipeUpVelocity(Math.max(1000, Math.abs(
-                        Math.round(settleVelocity * FLING_VELOCITY_MULTIPLIER))));
-            }
-        });
-    }
-
-    /** Invoked when the container is pulled. */
-    public void onPull(float deltaDistance, float displacement) {
-        absorbPullDeltaDistance(PULL_MULTIPLIER * deltaDistance, PULL_MULTIPLIER * displacement);
-        // Current motion spec is to actually push and not pull
-        // on this surface. However, until EdgeEffect.onPush (b/190612804) is
-        // implemented at view level, we will simply pull
-    }
-
-    @Override
-    public void getDrawingRect(Rect outRect) {
-        super.getDrawingRect(outRect);
-        outRect.offset(0, (int) getTranslationY());
-    }
-
-    @Override
-    public void setTranslationY(float translationY) {
-        super.setTranslationY(translationY);
-        invalidateHeader();
-    }
-
-    public void setScrimView(ScrimView scrimView) {
-        mScrimView = scrimView;
-    }
-
-    @Override
-    public void drawOnScrimWithScale(Canvas canvas, float scale) {
-        boolean isTablet = mActivityContext.getDeviceProfile().isTablet;
-
-        // Draw full background panel for tablets.
-        if (isTablet) {
-            mHeaderPaint.setColor(mBottomSheetBackgroundColor);
-            View panel = (View) mBottomSheetBackground;
-            float translationY = ((View) panel.getParent()).getTranslationY();
-            mTmpRectF.set(panel.getLeft(), panel.getTop() + translationY,
-                    panel.getRight(), panel.getBottom());
-            mTmpPath.reset();
-            mTmpPath.addRoundRect(mTmpRectF, mBottomSheetCornerRadii, Direction.CW);
-            canvas.drawPath(mTmpPath, mHeaderPaint);
-        }
-
-        if (DEBUG_HEADER_PROTECTION) {
-            mHeaderPaint.setColor(Color.MAGENTA);
-            mHeaderPaint.setAlpha(255);
-        } else {
-            mHeaderPaint.setColor(mHeaderColor);
-            mHeaderPaint.setAlpha((int) (getAlpha() * Color.alpha(mHeaderColor)));
-        }
-        if (mHeaderPaint.getColor() == mScrimColor || mHeaderPaint.getColor() == 0) {
-            return;
-        }
-        final float offset = (getVisibleContainerView().getHeight() * (1 - scale) / 2);
-        final float bottom =
-                scale * (getHeaderBottom() + getVisibleContainerView().getPaddingTop()) + offset;
-        FloatingHeaderView headerView = getFloatingHeaderView();
-        if (isTablet) {
-            // Start adding header protection if search bar or tabs will attach to the top.
-            if (!FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get() || mUsingTabs) {
-                View panel = (View) mBottomSheetBackground;
-                float translationY = ((View) panel.getParent()).getTranslationY();
-                mTmpRectF.set(panel.getLeft(), panel.getTop() + translationY, panel.getRight(),
-                        bottom);
-                mTmpPath.reset();
-                mTmpPath.addRoundRect(mTmpRectF, mBottomSheetCornerRadii, Direction.CW);
-                canvas.drawPath(mTmpPath, mHeaderPaint);
-            }
-        } else {
-            canvas.drawRect(0, 0, canvas.getWidth(), bottom, mHeaderPaint);
-        }
-        int tabsHeight = headerView.getPeripheralProtectionHeight();
-        if (mTabsProtectionAlpha > 0 && tabsHeight != 0) {
-            if (DEBUG_HEADER_PROTECTION) {
-                mHeaderPaint.setColor(Color.BLUE);
-                mHeaderPaint.setAlpha(255);
-            } else {
-                mHeaderPaint.setAlpha((int) (getAlpha() * mTabsProtectionAlpha));
-            }
-            int left = 0;
-            int right = canvas.getWidth();
-            if (isTablet) {
-                left = mBottomSheetBackground.getLeft();
-                right = mBottomSheetBackground.getRight();
-            }
-            canvas.drawRect(left, bottom, right, bottom + tabsHeight, mHeaderPaint);
-        }
-    }
-
-    /**
-     * redraws header protection
-     */
-    public void invalidateHeader() {
-        if (mScrimView != null) {
-            mScrimView.invalidate();
-        }
-    }
-
-    protected void updateHeaderScroll(int scrolledOffset) {
-        float prog = Utilities.boundToRange((float) scrolledOffset / mHeaderThreshold, 0f, 1f);
-        int headerColor = getHeaderColor(prog);
-        int tabsAlpha = mHeader.getPeripheralProtectionHeight() == 0 ? 0
-                : (int) (Utilities.boundToRange(
-                        (scrolledOffset + mHeader.mSnappedScrolledY) / mHeaderThreshold, 0f, 1f)
-                        * 255);
-        if (headerColor != mHeaderColor || mTabsProtectionAlpha != tabsAlpha) {
-            mHeaderColor = headerColor;
-            mTabsProtectionAlpha = tabsAlpha;
-            invalidateHeader();
-        }
-    }
-
-    protected int getHeaderColor(float blendRatio) {
-        return ColorUtils.blendARGB(mScrimColor, mHeaderProtectionColor, blendRatio);
-    }
-
-    protected abstract BaseAllAppsAdapter<T> createAdapter(AlphabeticalAppsList<T> mAppsList,
-            BaseAdapterProvider[] adapterProviders);
-
-    public int getHeaderBottom() {
-        int bottom = (int) getTranslationY() + mHeader.getClipTop();
-        if (FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get()) {
-            if (mActivityContext.getDeviceProfile().isTablet) {
-                return bottom + mBottomSheetBackground.getTop();
-            }
-            return bottom;
-        }
-        return bottom + mHeader.getTop();
-    }
-
-    /**
-     * Returns a view that denotes the visible part of all apps container view.
-     */
-    public View getVisibleContainerView() {
-        return mActivityContext.getDeviceProfile().isTablet ? mBottomSheetBackground : this;
-    }
-
-    protected void onInitializeRecyclerView(RecyclerView rv) {
-        rv.addOnScrollListener(mScrollListener);
-    }
-
-    /**
-     * Returns {@code true} the All Apps UI is currently being displayed on the target surface and
-     * is interactive.
-     */
-    public abstract boolean isInAllApps();
-
-    /** Holds a {@link BaseAllAppsAdapter} and related fields. */
-    public class AdapterHolder {
-        public static final int MAIN = 0;
-        public static final int WORK = 1;
-        public static final int SEARCH = 2;
-
-        private final int mType;
-        public final BaseAllAppsAdapter<T> mAdapter;
-        final RecyclerView.LayoutManager mLayoutManager;
-        final AlphabeticalAppsList<T> mAppsList;
-        final Rect mPadding = new Rect();
-        AllAppsRecyclerView mRecyclerView;
-
-        AdapterHolder(int type) {
-            mType = type;
-            mAppsList = new AlphabeticalAppsList<>(mActivityContext,
-                    isSearch() ? null : mAllAppsStore,
-                    isWork() ? mWorkManager : null);
-            BaseAdapterProvider[] adapterProviders =
-                    new BaseAdapterProvider[]{mMainAdapterProvider};
-
-            mAdapter = createAdapter(mAppsList, adapterProviders);
-            mAppsList.setAdapter(mAdapter);
-            mLayoutManager = mAdapter.getLayoutManager();
-        }
-
-        void setup(@NonNull View rv, @Nullable Predicate<ItemInfo> matcher) {
-            mAppsList.updateItemFilter(matcher);
-            mRecyclerView = (AllAppsRecyclerView) rv;
-            mRecyclerView.setEdgeEffectFactory(createEdgeEffectFactory());
-            mRecyclerView.setApps(mAppsList);
-            mRecyclerView.setLayoutManager(mLayoutManager);
-            mRecyclerView.setAdapter(mAdapter);
-            mRecyclerView.setHasFixedSize(true);
-            // No animations will occur when changes occur to the items in this RecyclerView.
-            mRecyclerView.setItemAnimator(null);
-            onInitializeRecyclerView(mRecyclerView);
-            FocusedItemDecorator focusedItemDecorator = new FocusedItemDecorator(mRecyclerView);
-            mRecyclerView.addItemDecoration(focusedItemDecorator);
-            mAdapter.setIconFocusListener(focusedItemDecorator.getFocusListener());
-            applyPadding();
-        }
-
-        void applyPadding() {
-            if (mRecyclerView != null) {
-                int bottomOffset = 0;
-                if (isWork() && mWorkManager.getWorkModeSwitch() != null) {
-                    bottomOffset = mInsets.bottom + mWorkManager.getWorkModeSwitch().getHeight();
-                }
-                mRecyclerView.setPadding(mPadding.left, mPadding.top, mPadding.right,
-                        mPadding.bottom + bottomOffset);
-            }
-        }
-
-        private boolean isWork() {
-            return mType == WORK;
-        }
-
-        private boolean isSearch() {
-            return mType == SEARCH;
-        }
-    }
-}
diff --git a/src/com/android/launcher3/allapps/DiscoveryBounce.java b/src/com/android/launcher3/allapps/DiscoveryBounce.java
index be261f7..0188a47 100644
--- a/src/com/android/launcher3/allapps/DiscoveryBounce.java
+++ b/src/com/android/launcher3/allapps/DiscoveryBounce.java
@@ -81,10 +81,10 @@
     }
 
     @Override
-    public boolean onBackPressed() {
-        super.onBackPressed();
-        // Go back to the previous state (from a user's perspective this floating view isn't
-        // something to go back from).
+    public boolean canHandleBack() {
+        // Since DiscoveryBounce doesn't handle back, onBackInvoked() won't be called and we should
+        // close it without animation.
+        close(false);
         return false;
     }
 
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index c18f9e1..b3ea3ab 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -32,7 +32,7 @@
 
 import com.android.launcher3.Insettable;
 import com.android.launcher3.R;
-import com.android.launcher3.allapps.BaseAllAppsContainerView.AdapterHolder;
+import com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.views.ActivityContext;
@@ -72,8 +72,8 @@
                     moved(current);
                     applyVerticalMove();
                     if (headerCollapsed != mHeaderCollapsed) {
-                        BaseAllAppsContainerView<?> parent =
-                                (BaseAllAppsContainerView<?>) getParent();
+                        ActivityAllAppsContainerView<?> parent =
+                                (ActivityAllAppsContainerView<?>) getParent();
                         parent.invalidateHeader();
                     }
                 }
@@ -191,7 +191,7 @@
         updateExpectedHeight();
 
         if (mMaxTranslation != oldMaxHeight || mFloatingRowsCollapsed) {
-            BaseAllAppsContainerView<?> parent = (BaseAllAppsContainerView<?>) getParent();
+            ActivityAllAppsContainerView parent = (ActivityAllAppsContainerView) getParent();
             if (parent != null) {
                 parent.setupHeader();
             }
diff --git a/src/com/android/launcher3/allapps/WorkProfileManager.java b/src/com/android/launcher3/allapps/WorkProfileManager.java
index fa03905..f66ea34 100644
--- a/src/com/android/launcher3/allapps/WorkProfileManager.java
+++ b/src/com/android/launcher3/allapps/WorkProfileManager.java
@@ -16,11 +16,11 @@
 package com.android.launcher3.allapps;
 
 import static com.android.launcher3.LauncherPrefs.WORK_EDU_STEP;
+import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.MAIN;
+import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.SEARCH;
+import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.WORK;
 import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_WORK_DISABLED_CARD;
 import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_WORK_EDU_CARD;
-import static com.android.launcher3.allapps.BaseAllAppsContainerView.AdapterHolder.MAIN;
-import static com.android.launcher3.allapps.BaseAllAppsContainerView.AdapterHolder.SEARCH;
-import static com.android.launcher3.allapps.BaseAllAppsContainerView.AdapterHolder.WORK;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TURN_OFF_WORK_APPS_TAP;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
@@ -54,7 +54,7 @@
 import java.util.function.Predicate;
 
 /**
- * Companion class for {@link BaseAllAppsContainerView} to manage work tab and personal tab
+ * Companion class for {@link ActivityAllAppsContainerView} to manage work tab and personal tab
  * related
  * logic based on {@link WorkProfileState}?
  */
@@ -79,7 +79,7 @@
     public @interface WorkProfileState { }
 
     private final UserManager mUserManager;
-    private final BaseAllAppsContainerView<?> mAllApps;
+    private final ActivityAllAppsContainerView<?> mAllApps;
     private final Predicate<ItemInfo> mMatcher;
     private final StatsLogManager mStatsLogManager;
 
@@ -89,7 +89,7 @@
     private int mCurrentState;
 
     public WorkProfileManager(
-            UserManager userManager, BaseAllAppsContainerView<?> allApps,
+            UserManager userManager, ActivityAllAppsContainerView allApps,
             StatsLogManager statsLogManager) {
         mUserManager = userManager;
         mAllApps = allApps;
@@ -152,7 +152,7 @@
     }
 
     /**
-     * Creates and attaches for profile toggle button to {@link BaseAllAppsContainerView}
+     * Creates and attaches for profile toggle button to {@link ActivityAllAppsContainerView}
      */
     public boolean attachWorkModeSwitch() {
         if (!mAllApps.getAppsStore().hasModelFlag(
@@ -177,7 +177,7 @@
         return true;
     }
     /**
-     * Removes work profile toggle button from {@link BaseAllAppsContainerView}
+     * Removes work profile toggle button from {@link ActivityAllAppsContainerView}
      */
     public void detachWorkModeSwitch() {
         if (mWorkModeSwitch != null && mWorkModeSwitch.getParent() == mAllApps) {
@@ -195,7 +195,7 @@
         return mWorkModeSwitch;
     }
 
-    private BaseAllAppsContainerView<?>.AdapterHolder getAH() {
+    private ActivityAllAppsContainerView.AdapterHolder getAH() {
         return mAllApps.mAH.get(WORK);
     }
 
diff --git a/src/com/android/launcher3/allapps/search/SearchAdapterProvider.java b/src/com/android/launcher3/allapps/search/SearchAdapterProvider.java
index 3890741..4f7f9af 100644
--- a/src/com/android/launcher3/allapps/search/SearchAdapterProvider.java
+++ b/src/com/android/launcher3/allapps/search/SearchAdapterProvider.java
@@ -16,7 +16,6 @@
 
 package com.android.launcher3.allapps.search;
 
-import android.net.Uri;
 import android.view.View;
 
 import androidx.recyclerview.widget.RecyclerView;
@@ -38,12 +37,6 @@
     }
 
     /**
-     * Called from LiveSearchManager to notify slice status updates.
-     */
-    public void onSliceStatusUpdate(Uri sliceUri) {
-    }
-
-    /**
      * Handles selection event on search adapter item. Returns false if provider can not handle
      * event
      */
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 94eea35..9a5d77e 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -1579,17 +1579,14 @@
         return getOpenView(activityContext, TYPE_FOLDER);
     }
 
-    /**
-     * Navigation bar back key or hardware input back key has been issued.
-     */
+    /** Navigation bar back key or hardware input back key has been issued. */
     @Override
-    public boolean onBackPressed() {
+    public void onBackInvoked() {
         if (isEditingName()) {
             mFolderName.dispatchBackKey();
         } else {
-            super.onBackPressed();
+            super.onBackInvoked();
         }
-        return true;
     }
 
     @Override
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
index a2353d8..56dffa9 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -175,8 +175,9 @@
         // Note: There should be at most one log per method call. This is enforced implicitly
         // by using if-else statements.
         AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this);
-        if (topView != null && topView.onBackPressed()) {
+        if (topView != null && topView.canHandleBack()) {
             // Handled by the floating view.
+            topView.onBackInvoked();
         } else {
             showAppDrawer(false);
         }
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index acb7eb3..9a34478 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -201,6 +201,13 @@
                 });
             }
 
+            case TestProtocol.REQUEST_WORKSPACE_COLUMNS_ROWS: {
+                return getLauncherUIProperty(Bundle::putParcelable, launcher -> new Point(
+                        InvariantDeviceProfile.INSTANCE.get(mContext).numColumns,
+                        InvariantDeviceProfile.INSTANCE.get(mContext).numRows)
+                );
+            }
+
             case TestProtocol.REQUEST_HOTSEAT_CELL_CENTER: {
                 final HotseatCellCenterRequest request = extra.getParcelable(
                         TestProtocol.TEST_INFO_REQUEST_FIELD);
diff --git a/src/com/android/launcher3/testing/shared/TestProtocol.java b/src/com/android/launcher3/testing/shared/TestProtocol.java
index f5ee91b..9b2ce9a 100644
--- a/src/com/android/launcher3/testing/shared/TestProtocol.java
+++ b/src/com/android/launcher3/testing/shared/TestProtocol.java
@@ -123,6 +123,7 @@
 
     public static final String REQUEST_WORKSPACE_CELL_LAYOUT_SIZE = "workspace-cell-layout-size";
     public static final String REQUEST_WORKSPACE_CELL_CENTER = "workspace-cell-center";
+    public static final String REQUEST_WORKSPACE_COLUMNS_ROWS = "workspace-columns-rows";
 
     public static final String REQUEST_HOTSEAT_CELL_CENTER = "hotseat-cell-center";
 
diff --git a/src/com/android/launcher3/testing/shared/WorkspaceCellCenterRequest.java b/src/com/android/launcher3/testing/shared/WorkspaceCellCenterRequest.java
index 80dbef8..e2cd8ea 100644
--- a/src/com/android/launcher3/testing/shared/WorkspaceCellCenterRequest.java
+++ b/src/com/android/launcher3/testing/shared/WorkspaceCellCenterRequest.java
@@ -124,7 +124,7 @@
          * Set span Height in cells
          */
         public WorkspaceCellCenterRequest.Builder setSpanY(int y) {
-            this.mCellY = y;
+            this.mSpanY = y;
             return this;
         }
 
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 38cdb4b..c78ecf5 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -17,7 +17,9 @@
 
 import static android.view.View.MeasureSpec.makeMeasureSpec;
 
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
+import static com.android.launcher3.allapps.AllAppsTransitionController.SWIPE_ALL_APPS_TO_HOME_MIN_SCALE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGETSTRAY_SEARCHED;
 import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
 
@@ -45,6 +47,7 @@
 import android.widget.Button;
 import android.widget.TextView;
 
+import androidx.annotation.FloatRange;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.recyclerview.widget.DefaultItemAnimator;
@@ -54,6 +57,7 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.model.UserManagerState;
@@ -264,6 +268,15 @@
         attachScrollbarToRecyclerView(currentRecyclerView);
     }
 
+    @Override
+    public void onBackProgressed(@FloatRange(from = 0.0, to = 1.0) float progress) {
+        float deceleratedProgress =
+                Interpolators.PREDICTIVE_BACK_DECELERATED_EASE.getInterpolation(progress);
+        float scaleProgress = SWIPE_ALL_APPS_TO_HOME_MIN_SCALE
+                + (1 - SWIPE_ALL_APPS_TO_HOME_MIN_SCALE) * (1 - deceleratedProgress);
+        SCALE_PROPERTY.set(this, scaleProgress);
+    }
+
     private void attachScrollbarToRecyclerView(WidgetsRecyclerView recyclerView) {
         recyclerView.bindFastScrollbar();
         if (mCurrentWidgetsRecyclerView != recyclerView) {
@@ -736,12 +749,12 @@
     }
 
     @Override
-    public boolean onBackPressed() {
+    public void onBackInvoked() {
         if (mIsInSearchMode) {
             mSearchBar.reset();
-            return true;
+        } else {
+            super.onBackInvoked();
         }
-        return super.onBackPressed();
     }
 
     @Override
diff --git a/tests/src/com/android/launcher3/celllayout/CellLayoutBoard.java b/tests/src/com/android/launcher3/celllayout/CellLayoutBoard.java
index 469d79f..6d75180 100644
--- a/tests/src/com/android/launcher3/celllayout/CellLayoutBoard.java
+++ b/tests/src/com/android/launcher3/celllayout/CellLayoutBoard.java
@@ -29,7 +29,73 @@
 import java.util.stream.Collectors;
 
 
-public class CellLayoutBoard {
+public class CellLayoutBoard implements Comparable<CellLayoutBoard> {
+
+    private boolean intersects(Rect r1, Rect r2) {
+        // If one rectangle is on left side of other
+        if (r1.left > r2.right || r2.left > r1.right) {
+            return false;
+        }
+
+        // If one rectangle is above other
+        if (r1.bottom > r2.top || r2.bottom > r1.top) {
+            return false;
+        }
+
+        return true;
+    }
+
+    private boolean overlapsWithIgnored(Set<Rect> ignoredRectangles, Rect rect) {
+        for (Rect ignoredRect : ignoredRectangles) {
+            // Using the built in intersects doesn't work because it doesn't account for area 0
+            if (intersects(ignoredRect, rect)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public int compareTo(CellLayoutBoard cellLayoutBoard) {
+        // to be equal they need to have the same number of widgets and the same dimensions
+        // their order can be different
+        Set<Rect> widgetsSet = new HashSet<>();
+        Set<Rect> ignoredRectangles = new HashSet<>();
+        for (WidgetRect rect : mWidgetsRects) {
+            if (rect.shouldIgnore()) {
+                ignoredRectangles.add(rect.mBounds);
+            } else {
+                widgetsSet.add(rect.mBounds);
+            }
+        }
+        for (WidgetRect rect : cellLayoutBoard.mWidgetsRects) {
+            // ignore rectangles overlapping with the area marked by x
+            if (overlapsWithIgnored(ignoredRectangles, rect.mBounds)) {
+                continue;
+            }
+            if (!widgetsSet.contains(rect.mBounds)) {
+                return -1;
+            }
+            widgetsSet.remove(rect.mBounds);
+        }
+        if (!widgetsSet.isEmpty()) {
+            return 1;
+        }
+
+        // to be equal they need to have the same number of icons their order can be different
+        Set<Point> iconsSet = new HashSet<>();
+        mIconPoints.forEach(icon -> iconsSet.add(icon.getCoord()));
+        for (IconPoint icon : cellLayoutBoard.mIconPoints) {
+            if (!iconsSet.contains(icon.getCoord())) {
+                return -1;
+            }
+            iconsSet.remove(icon.getCoord());
+        }
+        if (!iconsSet.isEmpty()) {
+            return 1;
+        }
+        return 0;
+    }
 
     public static class CellType {
         // The cells marked by this will be filled by 1x1 widgets and will be ignored when
@@ -115,7 +181,7 @@
     List<IconPoint> mIconPoints = new ArrayList<>();
     Map<Character, IconPoint> mIconsMap = new HashMap<>();
 
-    Point mMain = new Point();
+    WidgetRect mMain = null;
 
     CellLayoutBoard() {
         for (int x = 0; x < mWidget.length; x++) {
@@ -133,7 +199,7 @@
         return mIconPoints;
     }
 
-    public Point getMain() {
+    public WidgetRect getMain() {
         return mMain;
     }
 
@@ -273,6 +339,16 @@
         return iconPoints;
     }
 
+    public static WidgetRect getMainFromList(List<CellLayoutBoard> boards) {
+        for (CellLayoutBoard board : boards) {
+            WidgetRect main = board.getMain();
+            if (main != null) {
+                return main;
+            }
+        }
+        return null;
+    }
+
     public static CellLayoutBoard boardFromString(String boardStr) {
         String[] lines = boardStr.split("\n");
         CellLayoutBoard board = new CellLayoutBoard();
@@ -281,17 +357,18 @@
             String line = lines[y];
             for (int x = 0; x < line.length(); x++) {
                 char c = line.charAt(x);
-                if (c == CellType.MAIN_WIDGET) {
-                    board.mMain = new Point(x, y);
-                }
                 if (c != CellType.EMPTY) {
                     board.mWidget[x][y] = line.charAt(x);
                 }
             }
         }
         board.mWidgetsRects = getRects(board.mWidget);
-        board.mWidgetsRects.forEach(
-                widgetRect -> board.mWidgetsMap.put(widgetRect.mType, widgetRect));
+        board.mWidgetsRects.forEach(widgetRect -> {
+            if (widgetRect.mType == CellType.MAIN_WIDGET) {
+                board.mMain = widgetRect;
+            }
+            board.mWidgetsMap.put(widgetRect.mType, widgetRect);
+        });
         board.mIconPoints = getIconPoints(board.mWidget);
         return board;
     }
@@ -308,4 +385,24 @@
         }
         return s.toString();
     }
+
+    public static List<CellLayoutBoard> boardListFromString(String boardsStr) {
+        String[] lines = boardsStr.split("\n");
+        ArrayList<String> individualBoards = new ArrayList<>();
+        ArrayList<CellLayoutBoard> boards = new ArrayList<>();
+        for (String line : lines) {
+            String[] boardSegment = line.split("\\|");
+            for (int i = 0; i < boardSegment.length; i++) {
+                if (i >= individualBoards.size()) {
+                    individualBoards.add(boardSegment[i]);
+                } else {
+                    individualBoards.set(i, individualBoards.get(i) + "\n" + boardSegment[i]);
+                }
+            }
+        }
+        for (String board : individualBoards) {
+            boards.add(CellLayoutBoard.boardFromString(board));
+        }
+        return boards;
+    }
 }
diff --git a/tests/src/com/android/launcher3/celllayout/MultipleCellLayoutsSimpleReorder.java b/tests/src/com/android/launcher3/celllayout/MultipleCellLayoutsSimpleReorder.java
new file mode 100644
index 0000000..9b52fe8
--- /dev/null
+++ b/tests/src/com/android/launcher3/celllayout/MultipleCellLayoutsSimpleReorder.java
@@ -0,0 +1,88 @@
+/*
+ * 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.testcases;
+
+import android.graphics.Point;
+
+import java.util.Map;
+
+/**
+ * The grids represent the workspace to be build by TestWorkspaceBuilder, to see what each character
+ * in the board mean refer to {@code CellType}
+ */
+public class MultipleCellLayoutsSimpleReorder {
+
+    /** 5x5 Test
+     **/
+    private static final String START_BOARD_STR_5x5 = ""
+            + "xxxxx|-----\n"
+            + "--mm-|-----\n"
+            + "--mm-|-----\n"
+            + "-----|-----\n"
+            + "-----|-----";
+    private static final Point MOVE_TO_5x5 = new Point(8, 3);
+    private static final String END_BOARD_STR_5x5 = ""
+            + "xxxxx|-----\n"
+            + "-----|-----\n"
+            + "-----|-----\n"
+            + "-----|---mm\n"
+            + "-----|---mm";
+    private static final ReorderTestCase TEST_CASE_5x5 = new ReorderTestCase(START_BOARD_STR_5x5,
+            MOVE_TO_5x5,
+            END_BOARD_STR_5x5);
+
+    /** 4x4 Test
+     **/
+    private static final String START_BOARD_STR_4x4 = ""
+            + "xxxx|----\n"
+            + "--mm|----\n"
+            + "--mm|----\n"
+            + "----|----";
+    private static final Point MOVE_TO_4x4 = new Point(5, 3);
+    private static final String END_BOARD_STR_4x4 = ""
+            + "xxxx|----\n"
+            + "----|----\n"
+            + "----|-mm-\n"
+            + "----|-mm-";
+    private static final ReorderTestCase TEST_CASE_4x4 = new ReorderTestCase(START_BOARD_STR_4x4,
+            MOVE_TO_4x4,
+            END_BOARD_STR_4x4);
+
+
+    /** 6x5 Test
+     **/
+    private static final String START_BOARD_STR_6x5 = ""
+            + "xxxxxx|------\n"
+            + "--m---|------\n"
+            + "------|------\n"
+            + "------|------\n"
+            + "------|------";
+    private static final Point MOVE_TO_6x5 = new Point(10, 4);
+    private static final String END_BOARD_STR_6x5 = ""
+            + "xxxxxx|------\n"
+            + "------|------\n"
+            + "------|------\n"
+            + "------|------\n"
+            + "------|----m-";
+    private static final ReorderTestCase TEST_CASE_6x5 = new ReorderTestCase(START_BOARD_STR_6x5,
+            MOVE_TO_6x5,
+            END_BOARD_STR_6x5);
+
+    public static final Map<Point, ReorderTestCase> TEST_BY_GRID_SIZE =
+            Map.of(new Point(5, 5), TEST_CASE_5x5,
+                    new Point(4, 4), TEST_CASE_4x4,
+                    new Point(6, 5), TEST_CASE_6x5);
+}
diff --git a/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java b/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java
index 7b38ed6..35f6cbc 100644
--- a/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java
+++ b/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java
@@ -29,6 +29,7 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.celllayout.testcases.FullReorderCase;
 import com.android.launcher3.celllayout.testcases.MoveOutReorderCase;
+import com.android.launcher3.celllayout.testcases.MultipleCellLayoutsSimpleReorder;
 import com.android.launcher3.celllayout.testcases.PushReorderCase;
 import com.android.launcher3.celllayout.testcases.ReorderTestCase;
 import com.android.launcher3.celllayout.testcases.SimpleReorderCase;
@@ -38,7 +39,6 @@
 import com.android.launcher3.ui.TaplTestsLauncher3;
 import com.android.launcher3.util.rule.ShellCommandRule;
 import com.android.launcher3.views.DoubleShadowBubbleTextView;
-import com.android.launcher3.widget.LauncherAppWidgetHostView;
 
 import org.junit.Assume;
 import org.junit.Before;
@@ -48,6 +48,7 @@
 import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ExecutionException;
 
@@ -62,20 +63,6 @@
 
     TestWorkspaceBuilder mWorkspaceBuilder;
 
-    private View getViewAt(int cellX, int cellY) {
-        return getFromLauncher(l -> l.getWorkspace().getScreenWithId(
-                l.getWorkspace().getScreenIdForPageIndex(0)).getChildAt(cellX, cellY));
-    }
-
-    private Point getCellDimensions() {
-        return getFromLauncher(l -> {
-            CellLayout cellLayout = l.getWorkspace().getScreenWithId(
-                    l.getWorkspace().getScreenIdForPageIndex(0));
-            return new Point(cellLayout.getWidth() / cellLayout.getCountX(),
-                    cellLayout.getHeight() / cellLayout.getCountY());
-        });
-    }
-
     @Before
     public void setup() throws Throwable {
         mWorkspaceBuilder = new TestWorkspaceBuilder(this, mTargetContext);
@@ -86,28 +73,26 @@
     /**
      * Validate if the given board represent the current CellLayout
      **/
-    private boolean validateBoard(CellLayoutBoard board) {
-        boolean match = true;
-        Point cellDimensions = getCellDimensions();
-        for (CellLayoutBoard.WidgetRect widgetRect : board.getWidgets()) {
-            if (widgetRect.shouldIgnore()) {
-                continue;
+    private boolean validateBoard(List<CellLayoutBoard> testBoards) {
+        ArrayList<CellLayoutBoard> workspaceBoards = workspaceToBoards();
+        if (workspaceBoards.size() < testBoards.size()) {
+            return false;
+        }
+        for (int i = 0; i < testBoards.size(); i++) {
+            if (testBoards.get(i).compareTo(workspaceBoards.get(i)) != 0) {
+                return false;
             }
-            View widget = getViewAt(widgetRect.getCellX(), widgetRect.getCellY());
-            assertTrue("The view selected at " + board + " is not a widget",
-                    widget instanceof LauncherAppWidgetHostView);
-            match &= widgetRect.getSpanX()
-                    == Math.round(widget.getWidth() / (float) cellDimensions.x);
-            match &= widgetRect.getSpanY()
-                    == Math.round(widget.getHeight() / (float) cellDimensions.y);
-            if (!match) return match;
         }
-        for (CellLayoutBoard.IconPoint iconPoint : board.getIcons()) {
-            View icon = getViewAt(iconPoint.getCoord().x, iconPoint.getCoord().y);
-            assertTrue("The view selected at " + iconPoint.coord + " is not an Icon",
-                    icon instanceof DoubleShadowBubbleTextView);
+        return true;
+    }
+
+    private FavoriteItemsTransaction buildWorkspaceFromBoards(List<CellLayoutBoard> boards,
+            FavoriteItemsTransaction transaction) {
+        for (int i = 0; i < boards.size(); i++) {
+            CellLayoutBoard board = boards.get(i);
+            mWorkspaceBuilder.buildFromBoard(board, transaction, i);
         }
-        return match;
+        return transaction;
     }
 
     private void printCurrentWorkspace() {
@@ -148,22 +133,25 @@
 
     private void runTestCase(ReorderTestCase testCase)
             throws ExecutionException, InterruptedException {
-        Point mainWidgetCellPos = testCase.mStart.getMain();
+        CellLayoutBoard.WidgetRect mainWidgetCellPos = CellLayoutBoard.getMainFromList(
+                testCase.mStart);
 
         FavoriteItemsTransaction transaction =
                 new FavoriteItemsTransaction(mTargetContext, this);
-        mWorkspaceBuilder.buildFromBoard(testCase.mStart, transaction).commit();
+        transaction = buildWorkspaceFromBoards(testCase.mStart, transaction);
+        transaction.commit();
         waitForLauncherCondition("Workspace didn't finish loading", l -> !l.isWorkspaceLoading());
-        Widget widget = mLauncher.getWorkspace().getWidgetAtCell(mainWidgetCellPos.x,
-                mainWidgetCellPos.y);
+        Widget widget = mLauncher.getWorkspace().getWidgetAtCell(mainWidgetCellPos.getCellX(),
+                mainWidgetCellPos.getCellY());
         assertNotNull(widget);
         WidgetResizeFrame resizeFrame = widget.dragWidgetToWorkspace(testCase.moveMainTo.x,
-                testCase.moveMainTo.y);
+                testCase.moveMainTo.y, mainWidgetCellPos.getSpanX(), mainWidgetCellPos.getSpanY());
         resizeFrame.dismiss();
 
         boolean isValid = false;
-        for (CellLayoutBoard board : testCase.mEnd) {
-            isValid |= validateBoard(board);
+        for (List<CellLayoutBoard> boards : testCase.mEnd) {
+            isValid |= validateBoard(boards);
+            if (isValid) break;
         }
         printCurrentWorkspace();
         assertTrue("Non of the valid boards match with the current state", isValid);
@@ -207,4 +195,11 @@
         runTestCaseMap(MoveOutReorderCase.TEST_BY_GRID_SIZE,
                 MoveOutReorderCase.class.getSimpleName());
     }
+
+    @Test
+    public void multipleCellLayoutsSimpleReorder() throws ExecutionException, InterruptedException {
+        Assume.assumeTrue("Test doesn't support foldables", !mLauncher.isTwoPanels());
+        runTestCaseMap(MultipleCellLayoutsSimpleReorder.TEST_BY_GRID_SIZE,
+                MultipleCellLayoutsSimpleReorder.class.getSimpleName());
+    }
 }
diff --git a/tests/src/com/android/launcher3/celllayout/TestWorkspaceBuilder.java b/tests/src/com/android/launcher3/celllayout/TestWorkspaceBuilder.java
index 7e3588b..5945605 100644
--- a/tests/src/com/android/launcher3/celllayout/TestWorkspaceBuilder.java
+++ b/tests/src/com/android/launcher3/celllayout/TestWorkspaceBuilder.java
@@ -15,7 +15,6 @@
  */
 package com.android.launcher3.celllayout;
 
-import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
 import static com.android.launcher3.util.WidgetUtils.createWidgetInfo;
 
 import android.content.ComponentName;
@@ -62,7 +61,7 @@
      * Fills the given rect in WidgetRect with 1x1 widgets. This is useful to equalize cases.
      */
     private FavoriteItemsTransaction fillWithWidgets(CellLayoutBoard.WidgetRect widgetRect,
-            FavoriteItemsTransaction transaction) {
+            FavoriteItemsTransaction transaction, int screenId) {
         int initX = widgetRect.getCellX();
         int initY = widgetRect.getCellY();
         for (int x = initX; x < initX + widgetRect.getSpanX(); x++) {
@@ -71,8 +70,7 @@
                     // this widgets are filling, we don't care if we can't place them
                     ItemInfo item = createWidgetInCell(
                             new CellLayoutBoard.WidgetRect(CellLayoutBoard.CellType.IGNORE,
-                                    new Rect(x, y, x, y))
-                    );
+                                    new Rect(x, y, x, y)), screenId);
                     transaction.addItem(item);
                 } catch (Exception e) {
                     Log.d(TAG, "Unable to place filling widget at " + x + "," + y);
@@ -94,11 +92,11 @@
     }
 
     private void addCorrespondingWidgetRect(CellLayoutBoard.WidgetRect widgetRect,
-            FavoriteItemsTransaction transaction) {
+            FavoriteItemsTransaction transaction, int screenId) {
         if (widgetRect.mType == 'x') {
-            fillWithWidgets(widgetRect, transaction);
+            fillWithWidgets(widgetRect, transaction, screenId);
         } else {
-            transaction.addItem(createWidgetInCell(widgetRect));
+            transaction.addItem(createWidgetInCell(widgetRect, screenId));
         }
     }
 
@@ -106,11 +104,11 @@
      * Builds the given board into the transaction
      */
     public FavoriteItemsTransaction buildFromBoard(CellLayoutBoard board,
-            FavoriteItemsTransaction transaction) {
+            FavoriteItemsTransaction transaction, final int screenId) {
         board.getWidgets().forEach(
-                (widgetRect) -> addCorrespondingWidgetRect(widgetRect, transaction));
+                (widgetRect) -> addCorrespondingWidgetRect(widgetRect, transaction, screenId));
         board.getIcons().forEach((iconPoint) ->
-                transaction.addItem(createIconInCell(iconPoint))
+                transaction.addItem(createIconInCell(iconPoint, screenId))
         );
         return transaction;
     }
@@ -127,7 +125,7 @@
         return transaction;
     }
 
-    private ItemInfo createWidgetInCell(CellLayoutBoard.WidgetRect widgetRect) {
+    private ItemInfo createWidgetInCell(CellLayoutBoard.WidgetRect widgetRect, int screenId) {
         LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(mTest, false);
         LauncherAppWidgetInfo item = createWidgetInfo(info,
                 ApplicationProvider.getApplicationContext(), true);
@@ -136,14 +134,14 @@
         item.cellY = widgetRect.getCellY();
         item.spanX = widgetRect.getSpanX();
         item.spanY = widgetRect.getSpanY();
-        item.screenId = FIRST_SCREEN_ID;
+        item.screenId = screenId;
         return item;
     }
 
-    private ItemInfo createIconInCell(CellLayoutBoard.IconPoint iconPoint) {
+    private ItemInfo createIconInCell(CellLayoutBoard.IconPoint iconPoint, int screenId) {
         WorkspaceItemInfo item = new WorkspaceItemInfo(getApp());
         item.id = getID();
-        item.screenId = FIRST_SCREEN_ID;
+        item.screenId = screenId;
         item.cellX = iconPoint.getCoord().x;
         item.cellY = iconPoint.getCoord().y;
         item.minSpanY = item.minSpanX = item.spanX = item.spanY = 1;
diff --git a/tests/src/com/android/launcher3/celllayout/testcases/ReorderTestCase.java b/tests/src/com/android/launcher3/celllayout/testcases/ReorderTestCase.java
index 0a28668..1689e47 100644
--- a/tests/src/com/android/launcher3/celllayout/testcases/ReorderTestCase.java
+++ b/tests/src/com/android/launcher3/celllayout/testcases/ReorderTestCase.java
@@ -24,23 +24,23 @@
 import java.util.stream.Collectors;
 
 public class ReorderTestCase {
-    public CellLayoutBoard mStart;
+    public List<CellLayoutBoard> mStart;
     public Point moveMainTo;
-    public List<CellLayoutBoard> mEnd;
+    public List<List<CellLayoutBoard>> mEnd;
 
-    ReorderTestCase(CellLayoutBoard start, Point moveMainTo, CellLayoutBoard ... end) {
+    ReorderTestCase(List<CellLayoutBoard> start, Point moveMainTo, List<CellLayoutBoard> ... end) {
         mStart = start;
         this.moveMainTo = moveMainTo;
         mEnd = Arrays.asList(end);
     }
 
     ReorderTestCase(String start, Point moveMainTo, String ... end) {
-        mStart = CellLayoutBoard.boardFromString(start);
+        mStart = CellLayoutBoard.boardListFromString(start);
         this.moveMainTo = moveMainTo;
         mEnd = Arrays
                 .asList(end)
                 .stream()
-                .map(CellLayoutBoard::boardFromString)
+                .map(CellLayoutBoard::boardListFromString)
                 .collect(Collectors.toList());
     }
 }
diff --git a/tests/src/com/android/launcher3/celllayout/testcases/SimpleReorderCase.java b/tests/src/com/android/launcher3/celllayout/testcases/SimpleReorderCase.java
index 546c48b..c9f6849 100644
--- a/tests/src/com/android/launcher3/celllayout/testcases/SimpleReorderCase.java
+++ b/tests/src/com/android/launcher3/celllayout/testcases/SimpleReorderCase.java
@@ -33,13 +33,13 @@
             + "--mm-\n"
             + "-----\n"
             + "-----";
-    private static final Point MOVE_TO_5x5 = new Point(4, 4);
+    private static final Point MOVE_TO_5x5 = new Point(0, 4);
     private static final String END_BOARD_STR_5x5 = ""
             + "xxxxx\n"
             + "-----\n"
             + "-----\n"
-            + "---mm\n"
-            + "---mm";
+            + "mm---\n"
+            + "mm---";
     private static final ReorderTestCase TEST_CASE_5x5 = new ReorderTestCase(START_BOARD_STR_5x5,
             MOVE_TO_5x5,
             END_BOARD_STR_5x5);
@@ -61,7 +61,27 @@
             MOVE_TO_4x4,
             END_BOARD_STR_4x4);
 
+    /** 6x5 Test
+     **/
+    private static final String START_BOARD_STR_6x5 = ""
+            + "xxxxxx\n"
+            + "-mm---\n"
+            + "-mm---\n"
+            + "------\n"
+            + "------";
+    private static final Point MOVE_TO_6x5 = new Point(4, 3);
+    private static final String END_BOARD_STR_6x5 = ""
+            + "xxxxxx\n"
+            + "------\n"
+            + "------\n"
+            + "----mm\n"
+            + "----mm";
+    private static final ReorderTestCase TEST_CASE_6x5 = new ReorderTestCase(START_BOARD_STR_6x5,
+            MOVE_TO_6x5,
+            END_BOARD_STR_6x5);
+
     public static final Map<Point, ReorderTestCase> TEST_BY_GRID_SIZE =
             Map.of(new Point(5, 5), TEST_CASE_5x5,
-                    new Point(4, 4), TEST_CASE_4x4);
+                    new Point(4, 4), TEST_CASE_4x4,
+                    new Point(6, 5), TEST_CASE_6x5);
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/Widget.java b/tests/tapl/com/android/launcher3/tapl/Widget.java
index 046308b..d440903 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widget.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widget.java
@@ -71,7 +71,7 @@
     public WidgetResizeFrame dragWidgetToWorkspace() {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
             return dragWidgetToWorkspace(/* configurable= */ false, /* acceptsConfig= */ false, -1,
-                    -1);
+                    -1, 1, 1);
         }
     }
 
@@ -80,12 +80,12 @@
      * cellY and returns the resize frame that is shown after the widget is added.
      */
     @NonNull
-    public WidgetResizeFrame dragWidgetToWorkspace(int cellX, int cellY) {
+    public WidgetResizeFrame dragWidgetToWorkspace(int cellX, int cellY, int spanX, int spanY) {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                      "Dragging widget to workspace cell " + cellX + "," + cellY)) {
             return dragWidgetToWorkspace(/* configurable= */ false, /* acceptsConfig= */ false,
-                    cellX, cellY);
+                    cellX, cellY, spanX, spanY);
         }
     }
 
@@ -98,7 +98,7 @@
     public WidgetResizeFrame dragConfigWidgetToWorkspace(boolean acceptsConfig) {
         // TODO(b/239438337, fransebas) add correct event checking for this case
         //try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
-        return dragWidgetToWorkspace(/* configurable= */ true, acceptsConfig, -1, -1);
+        return dragWidgetToWorkspace(/* configurable= */ true, acceptsConfig, -1, -1, 1, 1);
         //}
     }
 
@@ -110,18 +110,17 @@
      * @param cellX            X position in the CellLayout
      * @param cellY            Y position in the CellLayout
      */
-    private void dragToWorkspace(boolean startsActivity, boolean isWidgetShortcut, int cellX,
-            int cellY) {
+    private void dragToWorkspaceCellPosition(boolean startsActivity, boolean isWidgetShortcut,
+            int cellX, int cellY, int spanX, int spanY) {
         Launchable launchable = getLaunchable();
         LauncherInstrumentation launcher = launchable.mLauncher;
-        Workspace.dragIconToWorkspace(
+        Workspace.dragIconToWorkspaceCellPosition(
                 launcher,
                 launchable,
-                () -> Workspace.getCellCenter(launchable.mLauncher, cellX, cellY),
+                cellX, cellY, spanX, spanY,
                 startsActivity,
                 isWidgetShortcut,
                 launchable::addExpectedEventsForLongClick);
-
     }
 
     /**
@@ -144,13 +143,13 @@
      */
     @Nullable
     private WidgetResizeFrame dragWidgetToWorkspace(boolean configurable, boolean acceptsConfig,
-            int cellX, int cellY) {
+            int cellX, int cellY, int spanX, int spanY) {
         if (cellX == -1 || cellY == -1) {
             internalDragToWorkspace(/* startsActivity= */ configurable, /* isWidgetShortcut= */
                     false);
         } else {
-            dragToWorkspace(/* startsActivity= */ configurable, /* isWidgetShortcut= */ false,
-                    cellX, cellY);
+            dragToWorkspaceCellPosition(/* startsActivity= */ configurable, /* isWidgetShortcut= */
+                    false, cellX, cellY, spanX, spanY);
         }
 
         if (configurable) {
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 388955c..62665de 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -409,9 +409,15 @@
     }
 
     static Point getCellCenter(LauncherInstrumentation launcher, int cellX, int cellY) {
-        return launcher.getTestInfo(WorkspaceCellCenterRequest.builder().setCellX(
-                cellX).setCellY(cellY).build()).getParcelable(
-                TestProtocol.TEST_INFO_RESPONSE_FIELD);
+        return launcher.getTestInfo(WorkspaceCellCenterRequest.builder().setCellX(cellX).setCellY(
+                cellY).build()).getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+    }
+
+    static Point getCellCenter(LauncherInstrumentation launcher, int cellX, int cellY, int spanX,
+            int spanY) {
+        return launcher.getTestInfo(WorkspaceCellCenterRequest.builder().setCellX(cellX)
+                .setCellY(cellY).setSpanX(spanX).setSpanY(spanY).build())
+                .getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD);
     }
 
     static Point getHotseatCellCenter(LauncherInstrumentation launcher, int cellInd) {
@@ -419,6 +425,12 @@
                 .setCellInd(cellInd).build()).getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD);
     }
 
+    /** Returns the number of rows and columns in the workspace */
+    public Point getRowsAndCols() {
+        return mLauncher.getTestInfo(TestProtocol.REQUEST_WORKSPACE_COLUMNS_ROWS).getParcelable(
+                TestProtocol.TEST_INFO_RESPONSE_FIELD);
+    }
+
     /**
      * Finds folder icons in the current workspace.
      *
@@ -457,6 +469,19 @@
                 launcher, launchable, dest, expectLongClickEvents, expectDropEvents);
     }
 
+    static void dragIconToWorkspaceCellPosition(LauncherInstrumentation launcher,
+            Launchable launchable, int cellX, int cellY, int spanX, int spanY,
+            boolean startsActivity, boolean isWidgetShortcut, Runnable expectLongClickEvents) {
+        Runnable expectDropEvents = null;
+        if (startsActivity || isWidgetShortcut) {
+            expectDropEvents = () -> launcher.expectEvent(TestProtocol.SEQUENCE_MAIN,
+                    LauncherInstrumentation.EVENT_START);
+        }
+        dragIconToWorkspaceCellPosition(
+                launcher, launchable, cellX, cellY, spanX, spanY, true, expectLongClickEvents,
+                expectDropEvents);
+    }
+
     /**
      * Drag icon in workspace to else where and drop it immediately.
      * (There is no slow down time before drop event)
@@ -526,6 +551,51 @@
         }
     }
 
+    static void dragIconToWorkspaceCellPosition(
+            LauncherInstrumentation launcher,
+            Launchable launchable,
+            int cellX, int cellY, int spanX, int spanY,
+            boolean isDecelerating,
+            Runnable expectLongClickEvents,
+            @Nullable Runnable expectDropEvents) {
+        try (LauncherInstrumentation.Closable ignored = launcher.addContextLayer(
+                "want to drag icon to workspace")) {
+            Point rowsAndCols = launcher.getWorkspace().getRowsAndCols();
+            int destinationWorkspace = cellX / rowsAndCols.x;
+            cellX = cellX % rowsAndCols.x;
+
+            final long downTime = SystemClock.uptimeMillis();
+            Point dragStart = launchable.startDrag(
+                    downTime,
+                    expectLongClickEvents,
+                    /* runToSpringLoadedState= */ true);
+            Point targetDest = getCellCenter(launcher, cellX, cellY, spanX, spanY);
+            int displayX = launcher.getRealDisplaySize().x;
+
+            // Since the destination can be on another page, we need to drag to the edge first
+            // until we reach the target page
+            for (int i = 0; i < destinationWorkspace; i++) {
+                // Don't drag all the way to the edge to prevent touch events from getting out of
+                //screen bounds.
+                Point screenEdge = new Point(displayX - 1, targetDest.y);
+                Point finalDragStart = dragStart;
+                executeAndWaitForPageScroll(launcher,
+                        () -> launcher.movePointer(finalDragStart, screenEdge, DEFAULT_DRAG_STEPS,
+                                true, downTime, downTime, true,
+                                LauncherInstrumentation.GestureScope.INSIDE));
+                dragStart = screenEdge;
+            }
+
+            // targetDest.x is now between 0 and displayX so we found the target page,
+            // we just have to put move the icon to the destination and drop it
+            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);
+        }
+    }
+
     private static void executeAndWaitForPageScroll(LauncherInstrumentation launcher,
             Runnable command) {
         launcher.executeAndWaitForEvent(command,