Merge "Removing some unused code from All-Apps" into tm-dev
diff --git a/Android.bp b/Android.bp
index b3027bc..0a55675 100644
--- a/Android.bp
+++ b/Android.bp
@@ -228,7 +228,7 @@
     ],
 }
 
-// Common source files used to build go launcher
+// Common source files used to build go launcher except go/src files
 filegroup {
     name: "launcher-go-src-no-build-config",
     srcs: [
@@ -236,8 +236,6 @@
         "src/**/*.kt",
         "quickstep/src/**/*.java",
         "quickstep/src/**/*.kt",
-        "go/src/**/*.java",
-        "go/src/**/*.kt",
         "go/quickstep/src/**/*.java",
         "go/quickstep/src/**/*.kt",
     ],
diff --git a/go/src/com/android/launcher3/util/AbsGridOccupancy.java b/go/src/com/android/launcher3/util/AbsGridOccupancy.java
new file mode 100644
index 0000000..4a46bd1
--- /dev/null
+++ b/go/src/com/android/launcher3/util/AbsGridOccupancy.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util;
+
+/**
+ * Defines method to find the next vacant cell on a grid.
+ * This uses the default top-down, left-right approach and can be over-written through
+ * code swaps in different launchers.
+ */
+public abstract class AbsGridOccupancy {
+
+    /**
+     * Find the first vacant cell, if there is one.
+     *
+     * @param vacantOut Holds the x and y coordinate of the vacant cell
+     * @param spanX Horizontal cell span.
+     * @param spanY Vertical cell span.
+     *
+     * @return true if a vacant cell was found
+     */
+    protected boolean findVacantCell(int[] vacantOut, boolean[][] cells, int countX, int countY,
+            int spanX, int spanY) {
+        for (int y = 0; (y + spanY) <= countY; y++) {
+            for (int x = 0; (x + spanX) <= countX; x++) {
+                boolean available = !cells[x][y];
+                out:
+                for (int i = x; i < x + spanX; i++) {
+                    for (int j = y; j < y + spanY; j++) {
+                        available = available && !cells[i][j];
+                        if (!available) break out;
+                    }
+                }
+                if (available) {
+                    vacantOut[0] = x;
+                    vacantOut[1] = y;
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+}
diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto
index cf854ed..10eedc8 100644
--- a/protos/launcher_atom.proto
+++ b/protos/launcher_atom.proto
@@ -76,6 +76,9 @@
 
 // Represents the apps list sorted alphabetically inside the all-apps view.
 message AllAppsContainer {
+  oneof ParentContainer {
+    TaskBarContainer taskbar_container = 1;
+  }
 }
 
 message WidgetsContainer {
@@ -83,6 +86,9 @@
 
 // Represents the predicted apps row(top row) in the all-apps view.
 message PredictionContainer {
+  oneof ParentContainer {
+    TaskBarContainer taskbar_container = 1;
+  }
 }
 
 // Represents the apps container within search results.
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 5b912ad..097609f 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -1161,7 +1161,7 @@
                     new LauncherAnimationRunner(mHandler, mWallpaperOpenTransitionRunner,
                             false /* startAtFrontOfQueue */), mLauncher.getIApplicationThread());
             mLauncherOpenTransition.addHomeOpenCheck(mLauncher.getComponentName());
-            SystemUiProxy.INSTANCE.getNoCreate().registerRemoteTransition(mLauncherOpenTransition);
+            SystemUiProxy.INSTANCE.get(mLauncher).registerRemoteTransition(mLauncherOpenTransition);
         }
         if (mBackAnimationController != null) {
             mBackAnimationController.registerBackCallbacks(mHandler);
@@ -1172,7 +1172,7 @@
         unregisterRemoteAnimations();
         unregisterRemoteTransitions();
         mStartingWindowListener.setTransitionManager(null);
-        SystemUiProxy.INSTANCE.getNoCreate().setStartingWindowListener(null);
+        SystemUiProxy.INSTANCE.get(mLauncher).setStartingWindowListener(null);
     }
 
     private void unregisterRemoteAnimations() {
@@ -1196,7 +1196,7 @@
         }
         if (hasControlRemoteAppTransitionPermission()) {
             if (mLauncherOpenTransition == null) return;
-            SystemUiProxy.INSTANCE.getNoCreate().unregisterRemoteTransition(
+            SystemUiProxy.INSTANCE.get(mLauncher).unregisterRemoteTransition(
                     mLauncherOpenTransition);
             mLauncherOpenTransition = null;
             mWallpaperOpenTransitionRunner = null;
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 175a1d9..f65b907 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -210,7 +210,7 @@
                 0, 1));
         // Center nav buttons in new height for IME.
         float transForIme = (mContext.getDeviceProfile().taskbarSize
-                - mContext.getTaskbarHeightForIme()) / 2f;
+                - mControllers.taskbarInsetsController.getTaskbarHeightForIme()) / 2f;
         // For gesture nav, nav buttons only show for IME anyway so keep them translated down.
         float defaultButtonTransY = alwaysShowButtons ? 0 : transForIme;
         mPropertyHolders.add(new StatePropertyHolder(mTaskbarNavButtonTranslationYForIme,
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 89e54b8..9561b74 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -27,9 +27,6 @@
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_OPEN;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
-import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
-import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_EXTRA_NAVIGATION_BAR;
-import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_SIZE;
 
 import android.animation.AnimatorSet;
 import android.app.ActivityOptions;
@@ -39,7 +36,6 @@
 import android.content.pm.ActivityInfo.Config;
 import android.content.pm.LauncherApps;
 import android.content.res.Resources;
-import android.graphics.Insets;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.os.Process;
@@ -88,7 +84,6 @@
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.rotation.RotationButtonController;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.WindowManagerWrapper;
 import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
 
 import java.io.PrintWriter;
@@ -113,7 +108,6 @@
 
     private final WindowManager mWindowManager;
     private final @Nullable RoundedCorner mLeftCorner, mRightCorner;
-    private final int mTaskbarHeightForIme;
     private WindowManager.LayoutParams mWindowLayoutParams;
     private boolean mIsFullscreen;
     // The size we should return to when we call setTaskbarWindowFullscreen(false)
@@ -154,7 +148,6 @@
                 Settings.Secure.getUriFor(Settings.Secure.NAV_BAR_KIDS_MODE), 0);
 
         updateIconSize(resources);
-        mTaskbarHeightForIme = resources.getDimensionPixelSize(R.dimen.taskbar_ime_size);
 
         // Get display and corners first, as views might use them in constructor.
         Display display = windowContext.getDisplay();
@@ -202,29 +195,14 @@
                 new TaskbarAutohideSuspendController(this),
                 new TaskbarPopupController(this),
                 new TaskbarForceVisibleImmersiveController(this),
-                new TaskbarAllAppsController(this));
+                new TaskbarAllAppsController(this),
+                new TaskbarInsetsController(this));
     }
 
     public void init(TaskbarSharedState sharedState) {
         mLastRequestedNonFullscreenHeight = getDefaultTaskbarWindowHeight();
         mWindowLayoutParams = createDefaultWindowLayoutParams();
 
-        WindowManagerWrapper wmWrapper = WindowManagerWrapper.getInstance();
-        wmWrapper.setProvidesInsetsTypes(
-                mWindowLayoutParams,
-                new int[] { ITYPE_EXTRA_NAVIGATION_BAR, ITYPE_BOTTOM_TAPPABLE_ELEMENT }
-        );
-        // Adjust the frame by the rounded corners (ie. leaving just the bar as the inset) when
-        // the IME is showing
-        mWindowLayoutParams.providedInternalImeInsets = new Insets[ITYPE_SIZE];
-        final Insets reducingSize = Insets.of(0,
-                getDefaultTaskbarWindowHeight() - mTaskbarHeightForIme, 0, 0);
-        mWindowLayoutParams.providedInternalImeInsets[ITYPE_EXTRA_NAVIGATION_BAR] = reducingSize;
-        mWindowLayoutParams.providedInternalImeInsets[ITYPE_BOTTOM_TAPPABLE_ELEMENT] =
-                reducingSize;
-
-        mWindowLayoutParams.insetsRoundedCornerFrame = true;
-
         // Initialize controllers after all are constructed.
         mControllers.init(sharedState);
         updateSysuiStateFlags(sharedState.sysuiStateFlags, true /* fromInit */);
@@ -304,6 +282,10 @@
         return mRightCorner == null ? 0 : mRightCorner.getRadius();
     }
 
+    public WindowManager.LayoutParams getWindowLayoutParams() {
+        return mWindowLayoutParams;
+    }
+
     @Override
     public TaskbarDragLayer getDragLayer() {
         return mDragLayer;
@@ -380,6 +362,14 @@
             folderBuilder.clearHotseat();
             itemInfoBuilder.setContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
                     .setFolder(folderBuilder));
+        } else if (oldContainer.hasAllAppsContainer()) {
+            itemInfoBuilder.setContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
+                    .setAllAppsContainer(oldContainer.getAllAppsContainer().toBuilder()
+                            .setTaskbarContainer(LauncherAtom.TaskBarContainer.newBuilder())));
+        } else if (oldContainer.hasPredictionContainer()) {
+            itemInfoBuilder.setContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
+                    .setPredictionContainer(oldContainer.getPredictionContainer().toBuilder()
+                            .setTaskbarContainer(LauncherAtom.TaskBarContainer.newBuilder())));
         }
     }
 
@@ -569,14 +559,7 @@
             }
         }
         mWindowLayoutParams.height = height;
-        final Insets reducingSize =
-                Insets.of(0, height - mTaskbarHeightForIme, 0, 0);
-        if (mWindowLayoutParams.providedInternalImeInsets == null) {
-            mWindowLayoutParams.providedInternalImeInsets = new Insets[ITYPE_SIZE];
-        }
-        mWindowLayoutParams.providedInternalImeInsets[ITYPE_EXTRA_NAVIGATION_BAR] = reducingSize;
-        mWindowLayoutParams.providedInternalImeInsets[ITYPE_BOTTOM_TAPPABLE_ELEMENT] =
-                reducingSize;
+        mControllers.taskbarInsetsController.onTaskbarWindowHeightChanged();
         mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
     }
 
@@ -588,13 +571,6 @@
     }
 
     /**
-     * Returns the bottom insets taskbar provides to the IME when IME is visible.
-     */
-    public int getTaskbarHeightForIme() {
-        return mTaskbarHeightForIme;
-    }
-
-    /**
      * Either adds or removes {@link WindowManager.LayoutParams#FLAG_NOT_FOCUSABLE} on the taskbar
      * window.
      */
@@ -703,6 +679,7 @@
             }
         } else if (tag instanceof AppInfo) {
             startItemInfoActivity((AppInfo) tag);
+            mControllers.uiController.onTaskbarIconLaunched((AppInfo) tag);
         } else {
             Log.e(TAG, "Unknown type clicked: " + tag);
         }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
index a3586396..5d3a152 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -51,6 +51,7 @@
     public final TaskbarPopupController taskbarPopupController;
     public final TaskbarForceVisibleImmersiveController taskbarForceVisibleImmersiveController;
     public final TaskbarAllAppsController taskbarAllAppsController;
+    public final TaskbarInsetsController taskbarInsetsController;
 
     @Nullable private LoggableTaskbarController[] mControllersToLog = null;
 
@@ -76,7 +77,8 @@
             TaskbarAutohideSuspendController taskbarAutoHideSuspendController,
             TaskbarPopupController taskbarPopupController,
             TaskbarForceVisibleImmersiveController taskbarForceVisibleImmersiveController,
-            TaskbarAllAppsController taskbarAllAppsController) {
+            TaskbarAllAppsController taskbarAllAppsController,
+            TaskbarInsetsController taskbarInsetsController) {
         this.taskbarActivityContext = taskbarActivityContext;
         this.taskbarDragController = taskbarDragController;
         this.navButtonController = navButtonController;
@@ -94,6 +96,7 @@
         this.taskbarPopupController = taskbarPopupController;
         this.taskbarForceVisibleImmersiveController = taskbarForceVisibleImmersiveController;
         this.taskbarAllAppsController = taskbarAllAppsController;
+        this.taskbarInsetsController = taskbarInsetsController;
     }
 
     /**
@@ -119,13 +122,14 @@
         taskbarForceVisibleImmersiveController.init(this);
         taskbarAllAppsController.init(this, sharedState);
         navButtonController.init(this);
+        taskbarInsetsController.init(this);
 
         mControllersToLog = new LoggableTaskbarController[] {
                 taskbarDragController, navButtonController, navbarButtonsViewController,
                 taskbarDragLayerController, taskbarScrimViewController, taskbarViewController,
                 taskbarUnfoldAnimationController, taskbarKeyguardController,
                 stashedHandleViewController, taskbarStashController, taskbarEduController,
-                taskbarAutohideSuspendController, taskbarPopupController
+                taskbarAutohideSuspendController, taskbarPopupController, taskbarInsetsController
         };
 
         mAreAllControllersInitialized = true;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
index c7330d3..307f674 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
@@ -15,18 +15,10 @@
  */
 package com.android.launcher3.taskbar;
 
-import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
-import static com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_ALL_APPS;
-import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_CONTENT;
-import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_FRAME;
-import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_REGION;
-
 import android.content.res.Resources;
 import android.graphics.Rect;
 
-import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.R;
-import com.android.launcher3.anim.AlphaUpdateListener;
 import com.android.launcher3.util.TouchController;
 import com.android.quickstep.AnimatedFloat;
 import com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo;
@@ -168,37 +160,7 @@
          * @see InsetsInfo#setTouchableInsets(int)
          */
         public void updateInsetsTouchability(InsetsInfo insetsInfo) {
-            insetsInfo.touchableRegion.setEmpty();
-            // Always have nav buttons be touchable
-            mControllers.navbarButtonsViewController.addVisibleButtonsRegion(
-                    mTaskbarDragLayer, insetsInfo.touchableRegion);
-            boolean insetsIsTouchableRegion = true;
-
-            if (mTaskbarDragLayer.getAlpha() < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD) {
-                // Let touches pass through us.
-                insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
-            } else if (mControllers.navbarButtonsViewController.isImeVisible()) {
-                insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
-            } else if (!mControllers.uiController.isTaskbarTouchable()) {
-                // Let touches pass through us.
-                insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
-            } else if (mControllers.taskbarDragController.isSystemDragInProgress()) {
-                // Let touches pass through us.
-                insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
-            } else if (AbstractFloatingView.getOpenView(mActivity, TYPE_TASKBAR_ALL_APPS) != null) {
-                // Let touches pass through us.
-                insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
-            } else if (mControllers.taskbarViewController.areIconsVisible()
-                    || AbstractFloatingView.getOpenView(mActivity, TYPE_ALL) != null
-                    || mActivity.isNavBarKidsModeActive()) {
-                // Taskbar has some touchable elements, take over the full taskbar area
-                insetsInfo.setTouchableInsets(mActivity.isTaskbarWindowFullscreen()
-                        ? TOUCHABLE_INSETS_FRAME : TOUCHABLE_INSETS_CONTENT);
-                insetsIsTouchableRegion = false;
-            } else {
-                insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
-            }
-            mActivity.excludeFromMagnificationRegion(insetsIsTouchableRegion);
+            mControllers.taskbarInsetsController.updateInsetsTouchability(insetsInfo);
         }
 
         /**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
new file mode 100644
index 0000000..a2ff780
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar
+
+import android.graphics.Insets
+import android.view.WindowManager
+import com.android.launcher3.AbstractFloatingView
+import com.android.launcher3.R
+import com.android.launcher3.anim.AlphaUpdateListener
+import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
+import com.android.quickstep.KtR
+import com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo
+import com.android.systemui.shared.system.WindowManagerWrapper
+import com.android.systemui.shared.system.WindowManagerWrapper.*
+import java.io.PrintWriter
+
+/**
+ * Handles the insets that Taskbar provides to underlying apps and the IME.
+ */
+class TaskbarInsetsController(val context: TaskbarActivityContext): LoggableTaskbarController {
+
+    /** The bottom insets taskbar provides to the IME when IME is visible. */
+    val taskbarHeightForIme: Int = context.resources.getDimensionPixelSize(
+        KtR.dimen.taskbar_ime_size)
+
+    // Initialized in init.
+    private lateinit var controllers: TaskbarControllers
+    private lateinit var windowLayoutParams: WindowManager.LayoutParams
+
+    fun init(controllers: TaskbarControllers) {
+        this.controllers = controllers
+        windowLayoutParams = context.windowLayoutParams
+
+        val wmWrapper: WindowManagerWrapper = getInstance()
+        wmWrapper.setProvidesInsetsTypes(
+            windowLayoutParams,
+            intArrayOf(
+                ITYPE_EXTRA_NAVIGATION_BAR,
+                ITYPE_BOTTOM_TAPPABLE_ELEMENT
+            )
+        )
+
+        windowLayoutParams.providedInternalImeInsets = arrayOfNulls<Insets>(ITYPE_SIZE)
+
+        onTaskbarWindowHeightChanged()
+
+        windowLayoutParams.insetsRoundedCornerFrame = true
+    }
+
+    fun onTaskbarWindowHeightChanged() {
+        val reducingSize = Insets.of(0, windowLayoutParams.height - taskbarHeightForIme, 0, 0)
+        windowLayoutParams.providedInternalImeInsets[ITYPE_EXTRA_NAVIGATION_BAR] = reducingSize
+        windowLayoutParams.providedInternalImeInsets[ITYPE_BOTTOM_TAPPABLE_ELEMENT] = reducingSize
+    }
+
+    /**
+     * Called to update the touchable insets.
+     * @see InsetsInfo.setTouchableInsets
+     */
+    fun updateInsetsTouchability(insetsInfo: InsetsInfo) {
+        insetsInfo.touchableRegion.setEmpty()
+        // Always have nav buttons be touchable
+        controllers.navbarButtonsViewController.addVisibleButtonsRegion(
+            context.dragLayer, insetsInfo.touchableRegion
+        )
+        var insetsIsTouchableRegion = true
+        if (context.dragLayer.alpha < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD) {
+            // Let touches pass through us.
+            insetsInfo.setTouchableInsets(InsetsInfo.TOUCHABLE_INSETS_REGION)
+        } else if (controllers.navbarButtonsViewController.isImeVisible) {
+            insetsInfo.setTouchableInsets(InsetsInfo.TOUCHABLE_INSETS_REGION)
+        } else if (!controllers.uiController.isTaskbarTouchable) {
+            // Let touches pass through us.
+            insetsInfo.setTouchableInsets(InsetsInfo.TOUCHABLE_INSETS_REGION)
+        } else if (controllers.taskbarDragController.isSystemDragInProgress) {
+            // Let touches pass through us.
+            insetsInfo.setTouchableInsets(InsetsInfo.TOUCHABLE_INSETS_REGION)
+        } else if (AbstractFloatingView.getOpenView<AbstractFloatingView?>(
+                context,
+                AbstractFloatingView.TYPE_TASKBAR_ALL_APPS
+            ) != null
+        ) {
+            // Let touches pass through us.
+            insetsInfo.setTouchableInsets(InsetsInfo.TOUCHABLE_INSETS_REGION)
+        } else if (controllers.taskbarViewController.areIconsVisible()
+            || AbstractFloatingView.getOpenView<AbstractFloatingView?>(
+                context,
+                AbstractFloatingView.TYPE_ALL
+            ) != null
+            || context.isNavBarKidsModeActive
+        ) {
+            // Taskbar has some touchable elements, take over the full taskbar area
+            insetsInfo.setTouchableInsets(
+                if (context.isTaskbarWindowFullscreen) {
+                    InsetsInfo.TOUCHABLE_INSETS_FRAME
+                } else {
+                    InsetsInfo.TOUCHABLE_INSETS_CONTENT
+                }
+            )
+            insetsIsTouchableRegion = false
+        } else {
+            insetsInfo.setTouchableInsets(InsetsInfo.TOUCHABLE_INSETS_REGION)
+        }
+        context.excludeFromMagnificationRegion(insetsIsTouchableRegion)
+    }
+
+    override fun dumpLogs(prefix: String, pw: PrintWriter) {
+        pw.println(prefix + "TaskbarInsetsController:")
+        pw.println("$prefix\twindowHeight=${windowLayoutParams.height}")
+        pw.println("$prefix\tprovidedInternalImeInsets[ITYPE_EXTRA_NAVIGATION_BAR]=" +
+                "${windowLayoutParams.providedInternalImeInsets[ITYPE_EXTRA_NAVIGATION_BAR]}")
+        pw.println("$prefix\tprovidedInternalImeInsets[ITYPE_BOTTOM_TAPPABLE_ELEMENT]=" +
+                "${windowLayoutParams.providedInternalImeInsets[ITYPE_BOTTOM_TAPPABLE_ELEMENT]}")
+    }
+}
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 8291475..7548398 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -312,8 +312,8 @@
         if (!mTouchEnabled) {
             return true;
         }
-        if (mIconLayoutBounds.contains((int) event.getX(), (int) event.getY())) {
-            // Don't allow long pressing between icons.
+        if (mIconLayoutBounds.left <= event.getX() && event.getX() <= mIconLayoutBounds.right) {
+            // Don't allow long pressing between icons, or above/below them.
             return true;
         }
         if (mControllerCallbacks.onTouchEvent(event)) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index e1ce898..6416720 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -18,6 +18,7 @@
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.Utilities.squaredHypot;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP;
 import static com.android.quickstep.AnimatedFloat.VALUE;
 
 import android.annotation.NonNull;
@@ -341,7 +342,10 @@
         }
 
         public View.OnClickListener getAllAppsButtonClickListener() {
-            return v -> mControllers.taskbarAllAppsController.show();
+            return v -> {
+                mActivity.getStatsLogManager().logger().log(LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP);
+                mControllers.taskbarAllAppsController.show();
+            };
         }
 
         public View.OnLongClickListener getIconOnLongClickListener() {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index 7bfb76a..34a6821 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -21,7 +21,8 @@
 import static com.android.launcher3.LauncherAnimUtils.newCancelListener;
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PULL_BACK_PROGRESS;
+import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PULL_BACK_ALPHA;
+import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PULL_BACK_TRANSLATION;
 import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_EDU;
@@ -40,11 +41,9 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.touch.SingleAxisSwipeDetector;
 import com.android.launcher3.util.TouchController;
 import com.android.quickstep.TaskUtils;
@@ -148,16 +147,10 @@
             AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_TASK_MENU);
         } else if (mStartState == ALL_APPS) {
             AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
-            builder.setFloat(allAppsController, ALL_APPS_PULL_BACK_PROGRESS,
-                    -mPullbackDistance / allAppsController.getShiftRange(), PULLBACK_INTERPOLATOR);
-
-            // Slightly fade out all apps content to further distinguish from scrolling.
-            StateAnimationConfig config = new StateAnimationConfig();
-            config.duration = accuracy;
-            config.setInterpolator(StateAnimationConfig.ANIM_ALL_APPS_FADE, Interpolators
-                    .mapToProgress(PULLBACK_INTERPOLATOR, 0, 0.5f));
-
-            allAppsController.setAlphas(mEndState, config, builder);
+            builder.setFloat(allAppsController, ALL_APPS_PULL_BACK_TRANSLATION,
+                    -mPullbackDistance, PULLBACK_INTERPOLATOR);
+            builder.setFloat(allAppsController, ALL_APPS_PULL_BACK_ALPHA,
+                    0.5f, PULLBACK_INTERPOLATOR);
         }
         AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mLauncher);
         if (topView != null) {
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 2ae0646..b90e820 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -770,7 +770,7 @@
             // We will handle the sysui flags based on the centermost task view.
             mRecentsAnimationController.setUseLauncherSystemBarFlags(swipeUpThresholdPassed
                     ||  (quickswitchThresholdPassed && centermostTaskFlags != 0));
-            mRecentsAnimationController.setSplitScreenMinimized(swipeUpThresholdPassed);
+            mRecentsAnimationController.setSplitScreenMinimized(mContext, swipeUpThresholdPassed);
             // Provide a hint to WM the direction that we will be settling in case the animation
             // needs to be canceled
             mRecentsAnimationController.setWillFinishToHome(swipeUpThresholdPassed);
@@ -1006,35 +1006,41 @@
         return false;
     }
 
-    private GestureEndTarget calculateEndTarget(PointF velocity, float endVelocity, boolean isFling,
-            boolean isCancel) {
+    private GestureEndTarget calculateEndTarget(PointF velocity, float endVelocity,
+            boolean isFlingY, boolean isCancel) {
         if (mGestureState.isHandlingAtomicEvent()) {
             // Button mode, this is only used to go to recents
             return RECENTS;
         }
         final GestureEndTarget endTarget;
-        final boolean goingToNewTask;
+        final boolean canGoToNewTask;
         if (mRecentsView != null) {
             if (!hasTargets()) {
                 // If there are no running tasks, then we can assume that this is a continuation of
                 // the last gesture, but after the recents animation has finished
-                goingToNewTask = true;
+                canGoToNewTask = true;
             } else {
                 final int runningTaskIndex = mRecentsView.getRunningTaskIndex();
                 final int taskToLaunch = mRecentsView.getNextPage();
-                goingToNewTask = runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex;
+                canGoToNewTask = runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex;
             }
         } else {
-            goingToNewTask = false;
+            canGoToNewTask = false;
         }
         final boolean reachedOverviewThreshold = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW;
-        if (!isFling) {
+        final boolean isFlingX = Math.abs(velocity.x) > mContext.getResources()
+                .getDimension(R.dimen.quickstep_fling_threshold_speed);
+        if (!isFlingY) {
             if (isCancel) {
                 endTarget = LAST_TASK;
             } else if (mDeviceState.isFullyGesturalNavMode()) {
-                if (mIsMotionPaused) {
+                if (canGoToNewTask && isFlingX) {
+                    // Flinging towards new task takes precedence over mIsMotionPaused (which only
+                    // checks y-velocity).
+                    endTarget = NEW_TASK;
+                } else if (mIsMotionPaused) {
                     endTarget = RECENTS;
-                } else if (goingToNewTask) {
+                } else if (canGoToNewTask) {
                     endTarget = NEW_TASK;
                 } else {
                     endTarget = !reachedOverviewThreshold ? LAST_TASK : HOME;
@@ -1042,26 +1048,22 @@
             } else {
                 endTarget = reachedOverviewThreshold && mGestureStarted
                         ? RECENTS
-                        : goingToNewTask
+                        : canGoToNewTask
                                 ? NEW_TASK
                                 : LAST_TASK;
             }
         } else {
             // If swiping at a diagonal, base end target on the faster velocity.
             boolean isSwipeUp = endVelocity < 0;
-            boolean willGoToNewTaskOnSwipeUp =
-                    goingToNewTask && Math.abs(velocity.x) > Math.abs(endVelocity);
+            boolean willGoToNewTask =
+                    canGoToNewTask && Math.abs(velocity.x) > Math.abs(endVelocity);
 
-            if (mDeviceState.isFullyGesturalNavMode() && isSwipeUp && !willGoToNewTaskOnSwipeUp) {
-                endTarget = HOME;
-            } else if (mDeviceState.isFullyGesturalNavMode() && isSwipeUp) {
-                // If swiping at a diagonal, base end target on the faster velocity.
-                endTarget = NEW_TASK;
+            if (mDeviceState.isFullyGesturalNavMode() && isSwipeUp) {
+                endTarget = willGoToNewTask ? NEW_TASK : HOME;
             } else if (isSwipeUp) {
-                endTarget = !reachedOverviewThreshold && willGoToNewTaskOnSwipeUp
-                        ? NEW_TASK : RECENTS;
+                endTarget = (!reachedOverviewThreshold && willGoToNewTask) ? NEW_TASK : RECENTS;
             } else {
-                endTarget = goingToNewTask ? NEW_TASK : LAST_TASK;
+                endTarget = willGoToNewTask ? NEW_TASK : LAST_TASK; // Swipe is downward.
             }
         }
 
@@ -1139,6 +1141,8 @@
                     duration = Math.max(duration, mRecentsView.getScroller().getDuration());
                 }
             }
+        } else if (endTarget == LAST_TASK && mRecentsView != null) {
+            mRecentsView.snapToPage(mRecentsView.getCurrentPage(), Math.toIntExact(duration));
         }
 
         // Let RecentsView handle the scrolling to the task, which we launch in startNewTask()
diff --git a/quickstep/src/com/android/quickstep/KtR.java b/quickstep/src/com/android/quickstep/KtR.java
index a768ef5..758c6e0 100644
--- a/quickstep/src/com/android/quickstep/KtR.java
+++ b/quickstep/src/com/android/quickstep/KtR.java
@@ -31,6 +31,7 @@
     public static final class dimen {
         public static int task_menu_spacing = R.dimen.task_menu_spacing;
         public static int task_menu_horizontal_padding = R.dimen.task_menu_horizontal_padding;
+        public static int taskbar_ime_size = R.dimen.taskbar_ime_size;
     }
 
     public static final class layout {
diff --git a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
index cc79f4a..921674a 100644
--- a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
+++ b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
@@ -29,7 +29,6 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.os.Handler;
-import android.util.Log;
 import android.util.MathUtils;
 import android.util.Pair;
 import android.view.RemoteAnimationTarget;
@@ -55,7 +54,7 @@
  * the app window and plays the rest of app close transitions in one go.
  *
  * This animation is used only for apps that enable back dispatching via
- * {@link android.view.OnBackInvokedDispatcher}. The controller registers
+ * {@link android.window.OnBackInvokedDispatcher}. The controller registers
  * an {@link IOnBackInvokedCallback} with WM Shell and receives back dispatches when a back
  * navigation to launcher starts.
  *
@@ -66,7 +65,6 @@
 public class LauncherBackAnimationController {
     private static final int CANCEL_TRANSITION_DURATION = 233;
     private static final float MIN_WINDOW_SCALE = 0.7f;
-    private static final String TAG = "LauncherBackAnimationController";
     private final QuickstepTransitionManager mQuickstepTransitionManager;
     private final Matrix mTransformMatrix = new Matrix();
     /** The window position at the beginning of the back animation. */
@@ -115,12 +113,7 @@
      * @param handler Handler to the thread to run the animations on.
      */
     public void registerBackCallbacks(Handler handler) {
-        SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.getNoCreate();
-        if (systemUiProxy == null) {
-            Log.e(TAG, "SystemUiProxy is null. Skip registering back invocation callbacks");
-            return;
-        }
-        systemUiProxy.setBackToLauncherCallback(
+        SystemUiProxy.INSTANCE.get(mLauncher).setBackToLauncherCallback(
                 new IOnBackInvokedCallback.Stub() {
                     @Override
                     public void onBackCancelled() {
@@ -170,10 +163,7 @@
 
     /** Unregisters the back to launcher callback in shell. */
     public void unregisterBackCallbacks() {
-        SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.getNoCreate();
-        if (systemUiProxy != null) {
-            systemUiProxy.clearBackToLauncherCallback();
-        }
+        SystemUiProxy.INSTANCE.get(mLauncher).clearBackToLauncherCallback();
     }
 
     private void startBack(BackEvent backEvent) {
@@ -298,10 +288,7 @@
         mInitialTouchPos.set(0, 0);
         mAnimatorSetInProgress = false;
         mSpringAnimationInProgress = false;
-        SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.getNoCreate();
-        if (systemUiProxy != null) {
-            SystemUiProxy.INSTANCE.getNoCreate().onBackToLauncherAnimationFinished();
-        }
+        SystemUiProxy.INSTANCE.get(mLauncher).onBackToLauncherAnimationFinished();
     }
 
     private void startTransitionAnimations(RectFSpringAnim springAnim, AnimatorSet anim) {
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index c120b32..2007ee1 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -19,6 +19,7 @@
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
 
+import android.content.Context;
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.IRecentsAnimationController;
@@ -97,24 +98,20 @@
      * Indicates that the gesture has crossed the window boundary threshold and we should minimize
      * if we are in splitscreen.
      */
-    public void setSplitScreenMinimized(boolean splitScreenMinimized) {
+    public void setSplitScreenMinimized(Context context, boolean splitScreenMinimized) {
         if (!mAllowMinimizeSplitScreen) {
             return;
         }
         if (mSplitScreenMinimized != splitScreenMinimized) {
             mSplitScreenMinimized = splitScreenMinimized;
-            UI_HELPER_EXECUTOR.execute(() -> {
-                SystemUiProxy p = SystemUiProxy.INSTANCE.getNoCreate();
-                if (p != null) {
-                    p.setSplitScreenMinimized(splitScreenMinimized);
-                }
-            });
+            UI_HELPER_EXECUTOR.execute(() -> SystemUiProxy.INSTANCE.get(context)
+                    .setSplitScreenMinimized(splitScreenMinimized));
         }
     }
 
     /**
      * Remove task remote animation target from
-     * {@link RecentsAnimationCallbacks#onTaskAppeared(RemoteAnimationTargetCompat)}}.
+     * {@link RecentsAnimationCallbacks#onTasksAppeared}}.
      */
     @UiThread
     public void removeTaskTarget(@NonNull RemoteAnimationTargetCompat target) {
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index b8334a9..f094d71 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -177,9 +177,8 @@
                     ((RecentsActivity) activityInterface.getCreatedActivity()).startHome();
                     return;
                 }
-                RemoteAnimationTarget[] nonAppTargets =
-                        SystemUiProxy.INSTANCE.getNoCreate()
-                                .onGoingToRecentsLegacy(false, nonHomeApps);
+                RemoteAnimationTarget[] nonAppTargets = SystemUiProxy.INSTANCE.get(mCtx)
+                        .onGoingToRecentsLegacy(false, nonHomeApps);
 
                 if (ENABLE_QUICKSTEP_LIVE_TILE.get() && activityInterface.isInLiveTileMode()
                         && activityInterface.getCreatedActivity() != null) {
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index dd459f5..11f0ff3 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -84,6 +84,9 @@
     public static final float QUICKSTEP_TOUCH_SLOP_RATIO_TWO_BUTTON = 9;
     public static final float QUICKSTEP_TOUCH_SLOP_RATIO_GESTURAL = 2;
 
+    // Minimum angle of a gesture's coordinate where a release goes to overview.
+    public static final int OVERVIEW_MIN_DEGREES = 15;
+
     private final RecentsAnimationDeviceState mDeviceState;
     private final NavBarPosition mNavBarPosition;
     private final TaskAnimationManager mTaskAnimationManager;
@@ -291,8 +294,9 @@
                 // the gesture (in which case mPassedPilferInputSlop starts as true).
                 boolean haveNotPassedSlopOnContinuedGesture =
                         !mPassedSlopOnThisGesture && mPassedPilferInputSlop;
+                double degrees = Math.toDegrees(Math.atan(upDist / horizontalDist));
                 boolean isLikelyToStartNewTask = haveNotPassedSlopOnContinuedGesture
-                        || horizontalDist > upDist;
+                        || degrees <= OVERVIEW_MIN_DEGREES;
 
                 if (!mPassedPilferInputSlop) {
                     if (passedSlop) {
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index 13007ea..10c56c9 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -20,6 +20,7 @@
 import static androidx.core.util.Preconditions.checkState;
 
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_NON_ACTIONABLE;
+import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.ALL_APPS_CONTAINER;
 import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.EXTENDED_CONTAINERS;
 import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.FOLDER;
 import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.SEARCH_RESULT_CONTAINER;
@@ -92,6 +93,7 @@
     private static final int FOLDER_HIERARCHY_OFFSET = 100;
     private static final int SEARCH_RESULT_HIERARCHY_OFFSET = 200;
     private static final int EXTENDED_CONTAINERS_HIERARCHY_OFFSET = 300;
+    private static final int ALL_APPS_HIERARCHY_OFFSET = 400;
 
     /**
      * Flags for converting SearchAttribute to integer value.
@@ -632,6 +634,9 @@
         } else if (info.getContainerInfo().getContainerCase() == EXTENDED_CONTAINERS) {
             return info.getContainerInfo().getExtendedContainers().getContainerCase().getNumber()
                     + EXTENDED_CONTAINERS_HIERARCHY_OFFSET;
+        } else if (info.getContainerInfo().getContainerCase() == ALL_APPS_CONTAINER) {
+            return info.getContainerInfo().getAllAppsContainer().getParentContainerCase()
+                    .getNumber() + ALL_APPS_HIERARCHY_OFFSET;
         } else {
             return info.getContainerInfo().getContainerCase().getNumber();
         }
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index 1631be0..6038a22 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -307,6 +307,7 @@
     private void initMultipleOrientationListeners() {
         mSharedPrefs.registerOnSharedPreferenceChangeListener(this);
         mSettingsCache.register(ROTATION_SETTING_URI, mRotationChangeListener);
+        updateAutoRotateSetting();
     }
 
     private void destroyMultipleOrientationListeners() {
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index 955fffc..244a794 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -26,6 +26,7 @@
 import com.android.quickstep.util.RecentsOrientedState;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 
 import java.util.HashMap;
 import java.util.function.Consumer;
@@ -171,8 +172,14 @@
         RunnableList endCallback = new RunnableList();
         RecentsView recentsView = getRecentsView();
         // Callbacks run from remote animation when recents animation not currently running
+        InteractionJankMonitorWrapper.begin(this,
+                InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER, "Enter form GroupedTaskView");
         recentsView.getSplitPlaceholder().launchTasks(this /*groupedTaskView*/,
-                success -> endCallback.executeAllAndDestroy(),
+                success -> {
+                    endCallback.executeAllAndDestroy();
+                    InteractionJankMonitorWrapper.end(
+                            InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
+                },
                 false /* freezeTaskList */);
 
         // Callbacks get run from recentsView for case when recents animation already running
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index aff9df6..49bf827 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -175,6 +175,7 @@
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 import com.android.systemui.shared.system.PackageManagerWrapper;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
@@ -1395,6 +1396,9 @@
         if (taskGroups == null || taskGroups.isEmpty()) {
             removeTasksViewsAndClearAllButton();
             onTaskStackUpdated();
+            // With all tasks removed, touch handling in PagedView is disabled and we need to reset
+            // touch state or otherwise values will be obsolete.
+            resetTouchState();
             return;
         }
 
@@ -2744,9 +2748,16 @@
             mFirstFloatingTaskView.addAnimation(anim, startingTaskRect,
                     mTempRect, true /* fadeWithThumbnail */, true /* isStagedTask */);
         }
+        InteractionJankMonitorWrapper.begin(this,
+                InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER, "First tile selected");
         anim.addEndListener(success -> {
             if (success) {
                 mSplitToast.show();
+                InteractionJankMonitorWrapper.end(
+                        InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
+            } else {
+                InteractionJankMonitorWrapper.cancel(
+                        InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
             }
         });
     }
@@ -4040,9 +4051,11 @@
         mSecondFloatingTaskView.setAlpha(1);
         mSecondFloatingTaskView.addAnimation(pendingAnimation, secondTaskStartingBounds,
                 secondTaskEndingBounds, true /* fadeWithThumbnail */, false /* isStagedTask */);
-        pendingAnimation.addEndListener(aBoolean ->
-                mSplitSelectStateController.launchSplitTasks(
-                        aBoolean1 -> RecentsView.this.resetFromSplitSelectionState()));
+        pendingAnimation.addEndListener(aBoolean -> {
+            mSplitSelectStateController.launchSplitTasks(
+                    aBoolean1 -> RecentsView.this.resetFromSplitSelectionState());
+            InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
+        });
         if (containerTaskView.containsMultipleTasks()) {
             // If we are launching from a child task, then only hide the thumbnail itself
             mSecondSplitHiddenView = thumbnailView;
@@ -4050,6 +4063,8 @@
             mSecondSplitHiddenView = containerTaskView;
         }
         mSecondSplitHiddenView.setVisibility(INVISIBLE);
+        InteractionJankMonitorWrapper.begin(this,
+                InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER, "Second tile selected");
         pendingAnimation.buildAnim().start();
         return true;
     }
@@ -4486,10 +4501,7 @@
             // Reset the minimized state since we force-toggled the minimized state when entering
             // overview, but never actually finished the recents animation.  This is a catch all for
             // cases where we haven't already reset it.
-            SystemUiProxy p = SystemUiProxy.INSTANCE.getNoCreate();
-            if (p != null) {
-                p.setSplitScreenMinimized(false);
-            }
+            SystemUiProxy.INSTANCE.get(getContext()).setSplitScreenMinimized(false);
         }
 
         if (mRecentsAnimationController == null) {
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 9c5c643..8869ff1 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -20,7 +20,6 @@
 import static android.widget.Toast.LENGTH_SHORT;
 
 import static com.android.launcher3.AbstractFloatingView.TYPE_TASK_MENU;
-import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
 import static com.android.launcher3.Utilities.comp;
 import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
@@ -628,10 +627,7 @@
 
             // Reset the minimized state since we force-toggled the minimized state when entering
             // overview, but never actually finished the recents animation
-            SystemUiProxy p = SystemUiProxy.INSTANCE.getNoCreate();
-            if (p != null) {
-                p.setSplitScreenMinimized(false);
-            }
+            SystemUiProxy.INSTANCE.get(getContext()).setSplitScreenMinimized(false);
 
             mIsClickableAsLiveTile = false;
             RemoteAnimationTargets targets;
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index ca6712f..f7600ff 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -57,6 +57,7 @@
 import com.android.launcher3.testcomponent.TestCommandReceiver;
 import com.android.launcher3.util.Wait;
 import com.android.launcher3.util.rule.FailureWatcher;
+import com.android.launcher3.util.rule.SamplerRule;
 import com.android.launcher3.util.rule.ScreenRecordRule;
 import com.android.quickstep.views.RecentsView;
 
@@ -111,7 +112,8 @@
         }
 
         mOrderSensitiveRules = RuleChain
-                .outerRule(new NavigationModeSwitchRule(mLauncher))
+                .outerRule(new SamplerRule())
+                .around(new NavigationModeSwitchRule(mLauncher))
                 .around(new FailureWatcher(mDevice, mLauncher));
 
         mOtherLauncherActivity = context.getPackageManager().queryIntentActivities(
diff --git a/res/drawable/gm_edit_24.xml b/res/drawable/gm_edit_24.xml
index 59a0dc2..f741333 100644
--- a/res/drawable/gm_edit_24.xml
+++ b/res/drawable/gm_edit_24.xml
@@ -5,6 +5,6 @@
     android:viewportHeight="24"
     android:tint="?attr/colorControlNormal">
   <path
-      android:fillColor="@android:color/white"
+      android:fillColor="?android:attr/textColorPrimaryInverse"
       android:pathData="M20.41,4.94l-1.35,-1.35c-0.78,-0.78 -2.05,-0.78 -2.83,0L3,16.82L3,21h4.18L20.41,7.77c0.79,-0.78 0.79,-2.05 0,-2.83zM6.41,19.06L5,19v-1.36l9.82,-9.82 1.41,1.41 -9.82,9.83z"/>
 </vector>
diff --git a/res/layout/widgets_full_sheet.xml b/res/layout/widgets_full_sheet.xml
index e867405..e3f1fca 100644
--- a/res/layout/widgets_full_sheet.xml
+++ b/res/layout/widgets_full_sheet.xml
@@ -38,12 +38,13 @@
             android:background="@drawable/bg_rounded_corner_bottom_sheet_handle"/>
 
         <TextView
+            style="@style/PrimaryHeadline"
             android:id="@+id/no_widgets_text"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:gravity="center"
             android:visibility="gone"
-            android:textSize="20sp"
+            android:textSize="18sp"
             android:layout_below="@id/search_and_recommendations_container"
             tools:text="No widgets available" />
 
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index ae190c5..774977c 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -32,7 +32,7 @@
     <string name="split_screen_position_left" msgid="7537793098851830883">"تقسيم لليسار"</string>
     <string name="split_screen_position_right" msgid="1569377524925193369">"تقسيم لليمين"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"‏معلومات تطبيق %1$s"</string>
-    <string name="long_press_widget_to_add" msgid="3587712543577675817">"انقر مع الاستمرار لنقل أداة"</string>
+    <string name="long_press_widget_to_add" msgid="3587712543577675817">"انقر مع الاستمرار لنقل أداة."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"انقر مرتين مع تثبيت إصبعك لنقل أداة أو استخدام الإجراءات المخصّصة."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"‏العرض %1$d الطول %2$d"</string>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index 49b34dd..ddcb256 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -47,7 +47,7 @@
     <string name="widgets_full_sheet_search_bar_hint" msgid="8484659090860596457">"Pretražite"</string>
     <string name="widgets_full_sheet_cancel_button_description" msgid="5766167035728653605">"Obrišite tekst iz okvira za pretragu"</string>
     <string name="no_widgets_available" msgid="4337693382501046170">"Vidžeti i prečice nisu dostupni"</string>
-    <string name="no_search_results" msgid="3787956167293097509">"Nije pronađen nijedan vidžet ili prečica"</string>
+    <string name="no_search_results" msgid="3787956167293097509">"Nije pronađen nijedan vidžet ni prečica"</string>
     <string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"Lično"</string>
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Posao"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Konverzacije"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 82031a6..3b38e62 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -32,7 +32,7 @@
     <string name="split_screen_position_left" msgid="7537793098851830883">"Rozdělit vlevo"</string>
     <string name="split_screen_position_right" msgid="1569377524925193369">"Rozdělit vpravo"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Informace o aplikaci %1$s"</string>
-    <string name="long_press_widget_to_add" msgid="3587712543577675817">"Klepnutím a podržením přesunete widget."</string>
+    <string name="long_press_widget_to_add" msgid="3587712543577675817">"Widget přesunete klepnutím a podržením."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Dvojitým klepnutím a podržením přesunete widget, případně použijte vlastní akce."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"šířka %1$d, výška %2$d"</string>
@@ -164,7 +164,7 @@
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Osobní"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Pracovní"</string>
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Pracovní profil"</string>
-    <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Pracovní aplikace jsou označené a viditelné vašemu administrátorovi IT"</string>
+    <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Pracovní aplikace jsou označené a váš administrátor IT je vidí"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"Rozumím"</string>
     <string name="work_apps_paused_title" msgid="3040901117349444598">"Pracovní aplikace jsou pozastaveny"</string>
     <string name="work_apps_paused_body" msgid="261634750995824906">"Pracovní aplikace vám nemohou zasílat oznámení, používat vaši baterii ani získat přístup k vaší poloze"</string>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
index e5eeb3b..90cd6cd 100644
--- a/res/values-en-rXC/strings.xml
+++ b/res/values-en-rXC/strings.xml
@@ -124,14 +124,10 @@
     <string name="app_installing_title" msgid="5864044122733792085">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‏‏‎‎‎‎‏‎‏‎‎‎‏‎‏‎‎‎‎‏‏‏‏‏‎‎‎‎‎‏‎‏‏‎‎‎‎‏‏‏‏‎‎‏‎‏‏‎‏‎‏‎‏‎‏‎‎‏‎‎‏‏‎<xliff:g id="NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ installing, ‎‏‎‎‏‏‎<xliff:g id="PROGRESS">%2$s</xliff:g>‎‏‎‎‏‏‏‎ complete‎‏‎‎‏‎"</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‏‏‎‎‎‏‏‏‏‎‏‎‏‏‎‎‏‎‏‏‎‎‏‎‎‏‏‏‏‎‎‎‏‎‏‏‏‏‎‎‏‏‏‎‏‏‎‏‎‏‎‏‎‎‎‎‏‎‎‏‏‎<xliff:g id="NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ downloading, ‎‏‎‎‏‏‎<xliff:g id="PROGRESS">%2$s</xliff:g>‎‏‎‎‏‏‏‎ complete‎‏‎‎‏‎"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‏‎‎‏‎‎‏‎‎‏‏‏‏‏‏‎‏‏‎‏‎‎‏‎‎‎‎‎‏‎‎‎‏‎‎‏‏‏‏‏‎‏‎‏‏‎‎‎‏‏‏‎‎‏‎‎‏‎‎‏‏‎<xliff:g id="NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ waiting to install‎‏‎‎‏‎"</string>
-    <!-- no translation found for dialog_update_title (114234265740994042) -->
-    <skip />
-    <!-- no translation found for dialog_update_message (4176784553982226114) -->
-    <skip />
-    <!-- no translation found for dialog_update (2178028071796141234) -->
-    <skip />
-    <!-- no translation found for dialog_remove (6510806469849709407) -->
-    <skip />
+    <string name="dialog_update_title" msgid="114234265740994042">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‎‎‏‏‏‎‎‏‎‏‎‏‏‏‎‏‎‏‏‏‎‏‏‏‎‏‎‏‏‎‎‏‏‏‏‎‏‎‏‎‏‎‏‏‎‏‏‎‎‏‎‏‏‏‏‏‏‎‏‎‎App update required‎‏‎‎‏‎"</string>
+    <string name="dialog_update_message" msgid="4176784553982226114">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‏‏‏‎‏‏‎‏‏‏‎‏‎‏‏‎‏‏‎‏‏‎‎‎‎‎‏‏‎‏‏‎‏‏‏‎‎‏‎‏‏‏‎‏‏‏‎‏‏‎‎‎‎‏‎‎The app for this icon isn\'t updated. You can update manually to re-enable this shortcut, or remove the icon.‎‏‎‎‏‎"</string>
+    <string name="dialog_update" msgid="2178028071796141234">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‎‏‏‏‏‏‎‎‎‏‏‏‎‎‏‏‏‏‎‏‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‏‎‏‎‎‎‏‏‏‏‎‎‏‏‏‎‏‏‎‎‏‎‏‏‎‎‏‎‎Update‎‏‎‎‏‎"</string>
+    <string name="dialog_remove" msgid="6510806469849709407">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‏‎‏‏‎‏‏‎‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‏‎‎‏‎‏‏‎‏‎‎‏‏‏‏‏‎‏‏‎‏‎‏‏‏‏‏‎Remove‎‏‎‎‏‎"</string>
     <string name="widgets_list" msgid="796804551140113767">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‎‎‏‎‏‏‎‎‎‎‏‏‏‎‏‏‎‏‎‎‎‏‏‎‎‎‎‏‎‎‏‎‎‎‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‏‏‏‎‏‎‏‏‎‎‏‏‏‎Widgets list‎‏‎‎‏‎"</string>
     <string name="widgets_list_closed" msgid="6141506579418771922">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‎‏‏‏‎‏‏‎‎‎‎‎‎‏‏‏‎‏‏‎‎‎‏‏‏‏‎‎‎‏‏‏‏‏‏‏‎‏‎‏‏‏‏‎‎‎‏‏‏‎‏‎‎‏‎‎Widgets list closed‎‏‎‎‏‎"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‎‏‎‏‎‏‏‏‏‏‏‏‏‎‎‎‎‎‎‏‎‏‎‎‎‏‏‏‎‏‏‎‏‏‎‎‎‏‏‎‎‏‏‏‏‏‎‎‎‏‎‏‎‏‏‏‏‎‏‎‎‎Add to home screen‎‏‎‎‏‎"</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 8572b67..a6f07da 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -23,8 +23,8 @@
     <string name="work_folder_name" msgid="3753320833950115786">"Trabajo"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"La aplicación no está instalada."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"La aplicación no está disponible"</string>
-    <string name="safemode_shortcut_error" msgid="9160126848219158407">"Aplicación descargada inhabilitada en modo seguro"</string>
-    <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets inhabilitados en modo seguro"</string>
+    <string name="safemode_shortcut_error" msgid="9160126848219158407">"Aplicación descargada inhabilitada en modo Seguro"</string>
+    <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets inhabilitados en modo Seguro"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Acceso directo no disponible"</string>
     <string name="home_screen" msgid="5629429142036709174">"Inicio"</string>
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Pantalla dividida"</string>
@@ -32,7 +32,7 @@
     <string name="split_screen_position_left" msgid="7537793098851830883">"Dividir parte izquierda"</string>
     <string name="split_screen_position_right" msgid="1569377524925193369">"Dividir parte derecha"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Información de la aplicación %1$s"</string>
-    <string name="long_press_widget_to_add" msgid="3587712543577675817">"Mantén pulsado un widget para moverlo."</string>
+    <string name="long_press_widget_to_add" msgid="3587712543577675817">"Mantén pulsado un widget para moverlo"</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Toca dos veces y mantén pulsado un widget para moverlo o usar acciones personalizadas."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d de ancho por %2$d de alto"</string>
@@ -41,7 +41,7 @@
     <string name="add_to_home_screen" msgid="9168649446635919791">"Añadir a pantalla de inicio"</string>
     <string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g> añadido a la pantalla de inicio"</string>
     <string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# widget}other{# widgets}}"</string>
-    <string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# combinación de teclas}other{# combinaciones de teclas}}"</string>
+    <string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# acceso directo}other{# accesos directos}}"</string>
     <string name="widgets_and_shortcuts_count" msgid="7209136747878365116">"<xliff:g id="WIDGETS_COUNT">%1$s</xliff:g>, <xliff:g id="SHORTCUTS_COUNT">%2$s</xliff:g>"</string>
     <string name="widget_button_text" msgid="2880537293434387943">"Widgets"</string>
     <string name="widgets_full_sheet_search_bar_hint" msgid="8484659090860596457">"Buscar"</string>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index fabe73c..b2d2654 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -164,12 +164,12 @@
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"મનગમતી ઍપ"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"ઑફિસની ઍપ"</string>
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"ઑફિસની પ્રોફાઇલ"</string>
-    <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"ઑફિસની ઍપને બૅજ આપેલા હોય છે અને તમારા IT વ્યવસ્થાપક તેમને જોઈ શકે છે"</string>
+    <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"ઑફિસની ઍપને બૅજ આપેલા હોય છે અને તમારા IT ઍડમિન તેમને જોઈ શકે છે"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"સમજાઈ ગયું"</string>
-    <string name="work_apps_paused_title" msgid="3040901117349444598">"ઑફિસ માટેની ઍપ થોભાવવામાં આવેલ છે"</string>
+    <string name="work_apps_paused_title" msgid="3040901117349444598">"ઑફિસ માટેની ઍપ થોભાવવામાં આવી છે"</string>
     <string name="work_apps_paused_body" msgid="261634750995824906">"ઑફિસ માટેની તમારી ઍપ તમને નોટિફિકેશન મોકલી શકતી નથી, તમારી બૅટરી વાપરી શકતી નથી કે તમારું સ્થાન ઍક્સેસ કરી શકતી નથી"</string>
     <string name="work_apps_paused_content_description" msgid="5149623040804051095">"ઑફિસ માટેની ઍપ બંધ છે. ઑફિસ માટેની તમારી ઍપ તમને નોટિફિકેશન મોકલી શકતી નથી, તમારી બૅટરી વાપરી શકતી નથી કે તમારું સ્થાન ઍક્સેસ કરી શકતી નથી"</string>
-    <string name="work_apps_paused_edu_banner" msgid="8872412121608402058">"ઑફિસની ઍપને બૅજ આપેલા હોય છે અને તમારા IT વ્યવસ્થાપક તેમને જોઈ શકે છે"</string>
+    <string name="work_apps_paused_edu_banner" msgid="8872412121608402058">"ઑફિસની ઍપને બૅજ આપેલા હોય છે અને તમારા IT ઍડમિન તેમને જોઈ શકે છે"</string>
     <string name="work_apps_paused_edu_accept" msgid="6377476824357318532">"સમજાઈ ગયું"</string>
     <string name="work_apps_pause_btn_text" msgid="1921059713673767460">"ઑફિસ માટેની ઍપ બંધ કરો"</string>
     <string name="work_apps_enable_btn_text" msgid="1156432622148413741">"ઑફિસ માટેની ઍપ ચાલુ કરો"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 9f21696..0aa3f11 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -32,7 +32,7 @@
     <string name="split_screen_position_left" msgid="7537793098851830883">"פיצול שמאלה"</string>
     <string name="split_screen_position_right" msgid="1569377524925193369">"פיצול ימינה"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"‏פרטים על האפליקציה %1$s"</string>
-    <string name="long_press_widget_to_add" msgid="3587712543577675817">"כדי להעביר ווידג\'ט למקום אחר יש לגעת ולא להרפות."</string>
+    <string name="long_press_widget_to_add" msgid="3587712543577675817">"להעברת ווידג\'ט למקום אחר לוחצים עליו לחיצה ארוכה."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"כדי להעביר ווידג\'ט למקום אחר או להשתמש בפעולות מותאמות אישית, יש ללחוץ פעמיים ולא להרפות."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"‏רוחב %1$d על גובה %2$d"</string>
@@ -161,7 +161,7 @@
     <string name="action_dismiss_notification" msgid="5909461085055959187">"סגירה"</string>
     <string name="accessibility_close" msgid="2277148124685870734">"סגירה"</string>
     <string name="notification_dismissed" msgid="6002233469409822874">"ההתראה נסגרה"</string>
-    <string name="all_apps_personal_tab" msgid="4190252696685155002">"אישיות"</string>
+    <string name="all_apps_personal_tab" msgid="4190252696685155002">"אישי"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"עבודה"</string>
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"פרופיל עבודה"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"‏האפליקציות לעבודה מתויגות ומוצגות למנהל ה-IT"</string>
@@ -171,7 +171,7 @@
     <string name="work_apps_paused_content_description" msgid="5149623040804051095">"האפליקציות לעבודה מושבתות. האפליקציות לא יכולות לשלוח לך התראות, להשתמש בסוללה או לגשת למיקום שלך"</string>
     <string name="work_apps_paused_edu_banner" msgid="8872412121608402058">"‏האפליקציות לעבודה מתויגות ומוצגות למנהל ה-IT"</string>
     <string name="work_apps_paused_edu_accept" msgid="6377476824357318532">"הבנתי"</string>
-    <string name="work_apps_pause_btn_text" msgid="1921059713673767460">"כיבוי של אפליקציות לעבודה"</string>
+    <string name="work_apps_pause_btn_text" msgid="1921059713673767460">"השבתה של אפליקציות לעבודה"</string>
     <string name="work_apps_enable_btn_text" msgid="1156432622148413741">"הפעלה של אפליקציות לעבודה"</string>
     <string name="developer_options_filter_hint" msgid="5896817443635989056">"סינון"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"הפעולה נכשלה: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 8ce6c73..416f204 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -164,7 +164,7 @@
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Personlig"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Jobb"</string>
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Jobbprofil"</string>
-    <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Jobbapper er merket og synlige for IT-administratoren din"</string>
+    <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Jobbapper er merket og synlige for IT-administratoren"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"Greit"</string>
     <string name="work_apps_paused_title" msgid="3040901117349444598">"Jobbapper er satt på pause"</string>
     <string name="work_apps_paused_body" msgid="261634750995824906">"Jobbapper kan ikke sende deg varsler, bruke batteriet eller få tilgang til posisjonen din"</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 5553e8c..6b5fe2b 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -47,7 +47,7 @@
     <string name="widgets_full_sheet_search_bar_hint" msgid="8484659090860596457">"Претражите"</string>
     <string name="widgets_full_sheet_cancel_button_description" msgid="5766167035728653605">"Обришите текст из оквира за претрагу"</string>
     <string name="no_widgets_available" msgid="4337693382501046170">"Виџети и пречице нису доступни"</string>
-    <string name="no_search_results" msgid="3787956167293097509">"Није пронађен ниједан виџет или пречица"</string>
+    <string name="no_search_results" msgid="3787956167293097509">"Није пронађен ниједан виџет ни пречица"</string>
     <string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"Лично"</string>
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Посао"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Конверзације"</string>
diff --git a/res/xml/device_profiles.xml b/res/xml/device_profiles.xml
index dd201e5..07ce598 100644
--- a/res/xml/device_profiles.xml
+++ b/res/xml/device_profiles.xml
@@ -145,6 +145,8 @@
         launcher:numFolderColumns="3"
         launcher:numHotseatIcons="6"
         launcher:numAllAppsColumns="6"
+        launcher:isScalable="true"
+        launcher:devicePaddingId="@xml/paddings_6x5"
         launcher:dbFile="launcher_6_by_5.db"
         launcher:defaultLayoutId="@xml/default_workspace_6x5"
         launcher:deviceCategory="tablet" >
@@ -153,14 +155,29 @@
             launcher:name="Tablet"
             launcher:minWidthDps="900"
             launcher:minHeightDps="820"
-            launcher:minCellHeight="104"
-            launcher:minCellWidth="80"
+            launcher:minCellHeight="120"
+            launcher:minCellWidth="102"
+            launcher:minCellHeightLandscape="104"
+            launcher:minCellWidthLandscape="120"
             launcher:iconImageSize="60"
             launcher:iconTextSize="14"
-            launcher:borderSpace="16"
+            launcher:borderSpaceHorizontal="16"
+            launcher:borderSpaceVertical="64"
+            launcher:borderSpaceLandscapeHorizontal="64"
+            launcher:borderSpaceLandscapeVertical="16"
+            launcher:horizontalMargin="54"
+            launcher:horizontalMarginLandscape="120"
+            launcher:allAppsCellWidth="96"
+            launcher:allAppsCellHeight="142"
+            launcher:allAppsCellWidthLandscape="126"
+            launcher:allAppsCellHeightLandscape="126"
             launcher:allAppsIconSize="60"
             launcher:allAppsIconTextSize="14"
-            launcher:allAppsBorderSpace="16"
+            launcher:allAppsBorderSpaceHorizontal="8"
+            launcher:allAppsBorderSpaceVertical="16"
+            launcher:allAppsBorderSpaceLandscape="16"
+            launcher:hotseatBorderSpace="58"
+            launcher:hotseatBorderSpaceLandscape="50.4"
             launcher:canBeDefault="true" />
 
     </grid-option>
diff --git a/res/xml/paddings_6x5.xml b/res/xml/paddings_6x5.xml
new file mode 100644
index 0000000..a958ec7
--- /dev/null
+++ b/res/xml/paddings_6x5.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<device-paddings xmlns:launcher="http://schemas.android.com/apk/res-auto" >
+
+    <!--  Some non default screen sizes  -->
+    <device-padding
+        launcher:maxEmptySpace="30dp">
+        <workspaceTopPadding
+            launcher:a="0.34"
+            launcher:b="0"/>
+        <workspaceBottomPadding
+            launcher:a="0.26"
+            launcher:b="0"/>
+        <hotseatBottomPadding
+            launcher:a="0.4"
+            launcher:b="0"/>
+    </device-padding>
+
+    <device-padding
+        launcher:maxEmptySpace="80dp">
+        <workspaceTopPadding
+            launcher:a="0"
+            launcher:b="20dp"/>
+        <workspaceBottomPadding
+            launcher:a="0.4"
+            launcher:b="0"
+            launcher:c="20dp"/>
+        <hotseatBottomPadding
+            launcher:a="0.6"
+            launcher:b="0"
+            launcher:c="20dp"/>
+    </device-padding>
+
+    <device-padding
+        launcher:maxEmptySpace="280dp">
+        <workspaceTopPadding
+            launcher:a="0"
+            launcher:b="112dp"/>
+        <workspaceBottomPadding
+            launcher:a="0.4"
+            launcher:b="0"
+            launcher:c="112dp"/>
+        <hotseatBottomPadding
+            launcher:a="0.6"
+            launcher:b="0"
+            launcher:c="112dp"/>
+    </device-padding>
+
+    <device-padding
+        launcher:maxEmptySpace="9999dp">
+        <workspaceTopPadding
+            launcher:a="0.40"
+            launcher:c="36dp"/>
+        <workspaceBottomPadding
+            launcher:a="0.60"
+            launcher:c="36dp"/>
+        <hotseatBottomPadding
+            launcher:a="0"
+            launcher:b="36dp"/>
+    </device-padding>
+</device-paddings>
\ No newline at end of file
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 5a59de5..33bb0a5 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -928,6 +928,13 @@
         return workspaceSpringLoadShrunkBottom;
     }
 
+    /**
+     * Gets the minimum visible amount of the next workspace page when in the spring-loaded state.
+     */
+    public float getWorkspaceSpringLoadedMinimumNextPageVisible() {
+        return getCellSize().x / 2f;
+    }
+
     public int getWorkspaceWidth() {
         return getWorkspaceWidth(getTotalWorkspacePadding());
     }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index fd9d0e1..b6a05b0 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -3235,12 +3235,12 @@
     /** Pauses view updates that should not be run during the app launch animation. */
     public void pauseExpensiveViewUpdates() {
         // Pause page indicator animations as they lead to layer trashing.
-        mWorkspace.getPageIndicator().pauseAnimations();
+        getWorkspace().getPageIndicator().pauseAnimations();
     }
 
     /** Resumes view updates at the end of the app launch animation. */
     public void resumeExpensiveViewUpdates() {
-        mWorkspace.getPageIndicator().skipAnimationsToEnd();
+        getWorkspace().getPageIndicator().skipAnimationsToEnd();
     }
 
 }
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index f5ac359..0a1d25c 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -1440,7 +1440,7 @@
         return Math.abs(velocity) > threshold;
     }
 
-    private void resetTouchState() {
+    protected void resetTouchState() {
         releaseVelocityTracker();
         mIsBeingDragged = false;
         mActivePointerId = INVALID_POINTER;
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 8662d00..637a418 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -44,6 +44,8 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.statemanager.StateManager.StateHandler;
 import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.util.MultiAdditivePropertyFactory;
+import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.UiThreadHelper;
 import com.android.launcher3.views.ScrimView;
 
@@ -76,20 +78,56 @@
                 }
             };
 
-    public static final FloatProperty<AllAppsTransitionController> ALL_APPS_PULL_BACK_PROGRESS =
-            new FloatProperty<AllAppsTransitionController>("allAppsPullBackProgress") {
+    public static final FloatProperty<AllAppsTransitionController> ALL_APPS_PULL_BACK_TRANSLATION =
+            new FloatProperty<AllAppsTransitionController>("allAppsPullBackTranslation") {
 
                 @Override
                 public Float get(AllAppsTransitionController controller) {
-                    return controller.mPullBackProgress;
+                    if (controller.mIsTablet) {
+                        return controller.mAppsView.getRecyclerViewContainer().getTranslationY();
+                    } else {
+                        return controller.getAppsViewPullbackTranslationY().get(
+                                controller.mAppsView);
+                    }
                 }
 
                 @Override
-                public void setValue(AllAppsTransitionController controller, float progress) {
-                    controller.setPullBackProgress(progress);
+                public void setValue(AllAppsTransitionController controller, float translation) {
+                    if (controller.mIsTablet) {
+                        controller.mAppsView.getRecyclerViewContainer().setTranslationY(
+                                translation);
+                    } else {
+                        controller.getAppsViewPullbackTranslationY().set(controller.mAppsView,
+                                translation);
+                    }
                 }
             };
 
+    public static final FloatProperty<AllAppsTransitionController> ALL_APPS_PULL_BACK_ALPHA =
+            new FloatProperty<AllAppsTransitionController>("allAppsPullBackAlpha") {
+
+                @Override
+                public Float get(AllAppsTransitionController controller) {
+                    if (controller.mIsTablet) {
+                        return controller.mAppsView.getRecyclerViewContainer().getAlpha();
+                    } else {
+                        return controller.getAppsViewPullbackAlpha().getValue();
+                    }
+                }
+
+                @Override
+                public void setValue(AllAppsTransitionController controller, float alpha) {
+                    if (controller.mIsTablet) {
+                        controller.mAppsView.getRecyclerViewContainer().setAlpha(alpha);
+                    } else {
+                        controller.getAppsViewPullbackAlpha().setValue(alpha);
+                    }
+                }
+            };
+
+    private static final int INDEX_APPS_VIEW_PROGRESS = 0;
+    private static final int INDEX_APPS_VIEW_PULLBACK = 1;
+    private static final int APPS_VIEW_INDEX_COUNT = 2;
 
     private ActivityAllAppsContainerView<Launcher> mAppsView;
 
@@ -104,18 +142,23 @@
     // When {@link mProgress} is 1, all apps container is pulled down.
     private float mShiftRange;      // changes depending on the orientation
     private float mProgress;        // [0, 1], mShiftRange * mProgress = shiftCurrent
-    private float mPullBackProgress;  // [0, 1], mShiftRange * mPullBackProgress = shiftCurrent
 
     private ScrimView mScrimView;
-    private View mPullBackView;
+
+    private final MultiAdditivePropertyFactory<View>
+            mAppsViewTranslationYPropertyFactory = new MultiAdditivePropertyFactory<>(
+            "appsViewTranslationY", View.TRANSLATION_Y);
+    private MultiValueAlpha mAppsViewAlpha;
+
+    private boolean mIsTablet;
 
     public AllAppsTransitionController(Launcher l) {
         mLauncher = l;
         DeviceProfile dp = mLauncher.getDeviceProfile();
         setShiftRange(dp.allAppsShiftRange);
         mProgress = 1f;
-        mPullBackProgress = 1f;
         mIsVerticalLayout = dp.isVerticalBarLayout();
+        mIsTablet = dp.isTablet;
         mLauncher.addOnDeviceProfileChangeListener(this);
     }
 
@@ -133,7 +176,7 @@
             mLauncher.getWorkspace().getPageIndicator().setTranslationY(0);
         }
 
-        mPullBackView = dp.isTablet ? mAppsView.getRecyclerViewContainer() : mAppsView;
+        mIsTablet = dp.isTablet;
     }
 
     /**
@@ -146,16 +189,27 @@
      */
     public void setProgress(float progress) {
         mProgress = progress;
-        mAppsView.setTranslationY(mProgress * mShiftRange);
+        getAppsViewProgressTranslationY().set(mAppsView, mProgress * mShiftRange);
     }
 
     public float getProgress() {
         return mProgress;
     }
 
-    private void setPullBackProgress(float progress) {
-        mPullBackProgress = progress;
-        mPullBackView.setTranslationY(mPullBackProgress * mShiftRange);
+    private FloatProperty<View> getAppsViewProgressTranslationY() {
+        return mAppsViewTranslationYPropertyFactory.get(INDEX_APPS_VIEW_PROGRESS);
+    }
+
+    private FloatProperty<View> getAppsViewPullbackTranslationY() {
+        return mAppsViewTranslationYPropertyFactory.get(INDEX_APPS_VIEW_PULLBACK);
+    }
+
+    private MultiValueAlpha.AlphaProperty getAppsViewProgressAlpha() {
+        return mAppsViewAlpha.getProperty(INDEX_APPS_VIEW_PROGRESS);
+    }
+
+    private MultiValueAlpha.AlphaProperty getAppsViewPullbackAlpha() {
+        return mAppsViewAlpha.getProperty(INDEX_APPS_VIEW_PULLBACK);
     }
 
     /**
@@ -164,8 +218,6 @@
      */
     @Override
     public void setState(LauncherState state) {
-        // Always reset pull back progress when switching states.
-        setPullBackProgress(0f);
         setProgress(state.getVerticalProgress(mLauncher));
         setAlphas(state, new StateAnimationConfig(), NO_ANIM_PROPERTY_SETTER);
         onProgressAnimationEnd();
@@ -180,10 +232,13 @@
             StateAnimationConfig config, PendingAnimation builder) {
         if (NORMAL.equals(toState) && mLauncher.isInState(ALL_APPS)) {
             UiThreadHelper.hideKeyboardAsync(mLauncher, mLauncher.getAppsView().getWindowToken());
+            builder.addEndListener(success -> {
+                // Reset pull back progress and alpha after switching states.
+                ALL_APPS_PULL_BACK_TRANSLATION.set(this, 0f);
+                ALL_APPS_PULL_BACK_ALPHA.set(this, 1f);
+            });
         }
 
-        // Always reset pull back progress when switching states.
-        setPullBackProgress(0f);
         float targetProgress = toState.getVerticalProgress(mLauncher);
         if (Float.compare(mProgress, targetProgress) == 0) {
             setAlphas(toState, config, builder);
@@ -222,7 +277,8 @@
         boolean hasAllAppsContent = (visibleElements & ALL_APPS_CONTENT) != 0;
 
         Interpolator allAppsFade = config.getInterpolator(ANIM_ALL_APPS_FADE, LINEAR);
-        setter.setViewAlpha(mAppsView, hasAllAppsContent ? 1 : 0, allAppsFade);
+        setter.setFloat(getAppsViewProgressAlpha(), MultiValueAlpha.VALUE,
+                hasAllAppsContent ? 1 : 0, allAppsFade);
 
         boolean shouldProtectHeader =
                 ALL_APPS == state || mLauncher.getStateManager().getState() == ALL_APPS;
@@ -245,8 +301,8 @@
                             | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
         }
         mAppsView.setScrimView(scrimView);
-        mPullBackView = mLauncher.getDeviceProfile().isTablet
-                ? mAppsView.getRecyclerViewContainer() : mAppsView;
+        mAppsViewAlpha = new MultiValueAlpha(mAppsView, APPS_VIEW_INDEX_COUNT);
+        mAppsViewAlpha.setUpdateVisibility(true);
     }
 
     /**
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 3dfece7..a11bd4f 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -23,7 +23,6 @@
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
 import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
 import static com.android.launcher3.model.ModelUtils.getMissingHotseatRanks;
-import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially;
 
 import android.annotation.TargetApi;
 import android.app.Fragment;
@@ -420,8 +419,6 @@
                 currentWorkspaceItems, otherWorkspaceItems);
         filterCurrentWorkspaceItems(currentScreenIds, dataModel.appWidgets, currentAppWidgets,
                 otherAppWidgets);
-
-        sortWorkspaceItemsSpatially(mIdp, currentWorkspaceItems);
         for (ItemInfo itemInfo : currentWorkspaceItems) {
             switch (itemInfo.itemType) {
                 case Favorites.ITEM_TYPE_APPLICATION:
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 7ba2317..fe9b633 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -42,8 +42,10 @@
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.SparseArray;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.core.util.Pair;
 
 import com.android.launcher3.InvariantDeviceProfile;
@@ -94,6 +96,8 @@
     private final InstantAppResolver mInstantAppResolver;
     private final IconProvider mIconProvider;
 
+    private final SparseArray<BitmapInfo> mWidgetCategoryBitmapInfos;
+
     private int mPendingIconRequestCount = 0;
 
     public IconCache(Context context, InvariantDeviceProfile idp) {
@@ -111,6 +115,7 @@
         mUserManager = UserCache.INSTANCE.get(mContext);
         mInstantAppResolver = InstantAppResolver.newInstance(mContext);
         mIconProvider = iconProvider;
+        mWidgetCategoryBitmapInfos = new SparseArray<>();
     }
 
     @Override
@@ -477,13 +482,39 @@
         CacheEntry entry = getEntryForPackageLocked(
                 infoInOut.packageName, infoInOut.user, useLowResIcon);
         applyCacheEntry(entry, infoInOut);
-        if (infoInOut.widgetCategory != NO_CATEGORY) {
-            WidgetSection widgetSection = WidgetSections.getWidgetSections(mContext)
-                    .get(infoInOut.widgetCategory);
-            infoInOut.title = mContext.getString(widgetSection.mSectionTitle);
-            infoInOut.contentDescription = mPackageManager.getUserBadgedLabel(
-                    infoInOut.title, infoInOut.user);
+        if (infoInOut.widgetCategory == NO_CATEGORY) {
+            return;
         }
+
+        WidgetSection widgetSection = WidgetSections.getWidgetSections(mContext)
+                .get(infoInOut.widgetCategory);
+        infoInOut.title = mContext.getString(widgetSection.mSectionTitle);
+        infoInOut.contentDescription = mPackageManager.getUserBadgedLabel(
+                infoInOut.title, infoInOut.user);
+        final BitmapInfo cachedBitmap = mWidgetCategoryBitmapInfos.get(infoInOut.widgetCategory);
+        if (cachedBitmap != null) {
+            infoInOut.bitmap = getBadgedIcon(cachedBitmap, infoInOut.user);
+            return;
+        }
+
+        try (LauncherIcons li = LauncherIcons.obtain(mContext)) {
+            final BitmapInfo tempBitmap = li.createBadgedIconBitmap(
+                    mContext.getDrawable(widgetSection.mSectionDrawable),
+                    new BaseIconFactory.IconOptions().setShrinkNonAdaptiveIcons(false));
+            mWidgetCategoryBitmapInfos.put(infoInOut.widgetCategory, tempBitmap);
+            infoInOut.bitmap = getBadgedIcon(tempBitmap, infoInOut.user);
+        } catch (Exception e) {
+            Log.e(TAG, "Error initializing bitmap for icons with widget category", e);
+        }
+
+    }
+
+    private synchronized BitmapInfo getBadgedIcon(@Nullable final BitmapInfo bitmap,
+            @NonNull final UserHandle user) {
+        if (bitmap == null) {
+            return getDefaultIcon(user);
+        }
+        return bitmap.withFlags(getUserFlagOpLocked(user));
     }
 
     protected void applyCacheEntry(CacheEntry entry, ItemInfoWithIcon info) {
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 9af72c3..2b6e426 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -586,6 +586,9 @@
 
         @UiEvent(doc = "User clicked on IME quicksearch button.")
         LAUNCHER_ALLAPPS_QUICK_SEARCH_WITH_IME(1047),
+
+        @UiEvent(doc = "User tapped taskbar All Apps button.")
+        LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP(1057),
         ;
 
         // ADD MORE
diff --git a/src/com/android/launcher3/model/BaseLoaderResults.java b/src/com/android/launcher3/model/BaseLoaderResults.java
index 5b278ab..6c4cfb9 100644
--- a/src/com/android/launcher3/model/BaseLoaderResults.java
+++ b/src/com/android/launcher3/model/BaseLoaderResults.java
@@ -18,7 +18,6 @@
 
 import static com.android.launcher3.model.ItemInstallQueue.FLAG_LOADER_RUNNING;
 import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
-import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
 import android.os.Process;
@@ -27,6 +26,8 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel.CallbackTask;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.model.BgDataModel.FixedContainerItems;
 import com.android.launcher3.model.data.AppInfo;
@@ -110,6 +111,42 @@
 
     public abstract void bindWidgets();
 
+    /**
+     * Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to right)
+     */
+    protected void sortWorkspaceItemsSpatially(InvariantDeviceProfile profile,
+            ArrayList<ItemInfo> workspaceItems) {
+        final int screenCols = profile.numColumns;
+        final int screenCellCount = profile.numColumns * profile.numRows;
+        Collections.sort(workspaceItems, (lhs, rhs) -> {
+            if (lhs.container == rhs.container) {
+                // Within containers, order by their spatial position in that container
+                switch (lhs.container) {
+                    case LauncherSettings.Favorites.CONTAINER_DESKTOP: {
+                        int lr = (lhs.screenId * screenCellCount + lhs.cellY * screenCols
+                                + lhs.cellX);
+                        int rr = (rhs.screenId * screenCellCount + +rhs.cellY * screenCols
+                                + rhs.cellX);
+                        return Integer.compare(lr, rr);
+                    }
+                    case LauncherSettings.Favorites.CONTAINER_HOTSEAT: {
+                        // We currently use the screen id as the rank
+                        return Integer.compare(lhs.screenId, rhs.screenId);
+                    }
+                    default:
+                        if (FeatureFlags.IS_STUDIO_BUILD) {
+                            throw new RuntimeException(
+                                    "Unexpected container type when sorting workspace items.");
+                        }
+                        return 0;
+                }
+            } else {
+                // Between containers, order by hotseat, desktop
+                return Integer.compare(lhs.container, rhs.container);
+            }
+        });
+    }
+
     protected void executeCallbacksTask(CallbackTask task, Executor executor) {
         executor.execute(() -> {
             if (mMyBindingId != mBgDataModel.lastBindId) {
@@ -131,7 +168,7 @@
         return idleLock;
     }
 
-    private static class WorkspaceBinder {
+    private class WorkspaceBinder {
 
         private final Executor mUiExecutor;
         private final Callbacks mCallbacks;
diff --git a/src/com/android/launcher3/model/ModelUtils.java b/src/com/android/launcher3/model/ModelUtils.java
index ef5eef1..df6768d 100644
--- a/src/com/android/launcher3/model/ModelUtils.java
+++ b/src/com/android/launcher3/model/ModelUtils.java
@@ -23,10 +23,8 @@
 import android.os.Process;
 import android.util.Log;
 
-import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.model.data.ItemInfo;
@@ -92,42 +90,6 @@
     }
 
     /**
-     * Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to right)
-     */
-    public static void sortWorkspaceItemsSpatially(InvariantDeviceProfile profile,
-            ArrayList<ItemInfo> workspaceItems) {
-        final int screenCols = profile.numColumns;
-        final int screenCellCount = profile.numColumns * profile.numRows;
-        Collections.sort(workspaceItems, (lhs, rhs) -> {
-            if (lhs.container == rhs.container) {
-                // Within containers, order by their spatial position in that container
-                switch (lhs.container) {
-                    case LauncherSettings.Favorites.CONTAINER_DESKTOP: {
-                        int lr = (lhs.screenId * screenCellCount + lhs.cellY * screenCols
-                                + lhs.cellX);
-                        int rr = (rhs.screenId * screenCellCount + +rhs.cellY * screenCols
-                                + rhs.cellX);
-                        return Integer.compare(lr, rr);
-                    }
-                    case LauncherSettings.Favorites.CONTAINER_HOTSEAT: {
-                        // We currently use the screen id as the rank
-                        return Integer.compare(lhs.screenId, rhs.screenId);
-                    }
-                    default:
-                        if (FeatureFlags.IS_STUDIO_BUILD) {
-                            throw new RuntimeException(
-                                    "Unexpected container type when sorting workspace items.");
-                        }
-                        return 0;
-                }
-            } else {
-                // Between containers, order by hotseat, desktop
-                return Integer.compare(lhs.container, rhs.container);
-            }
-        });
-    }
-
-    /**
      * Iterates though current workspace items and returns available hotseat ranks for prediction.
      */
     public static IntArray getMissingHotseatRanks(List<ItemInfo> items, int len) {
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index df8a3e2..196cc56 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -602,6 +602,7 @@
         mIsAboveIcon = y > dragLayer.getTop() + insets.top;
         if (!mIsAboveIcon) {
             y = mTempRect.top + iconHeight + extraVerticalSpace;
+            height -= extraVerticalSpace;
         }
 
         // Insets are added later, so subtract them now.
@@ -609,7 +610,7 @@
         y -= insets.top;
 
         mGravity = 0;
-        if (y + height > dragLayer.getBottom() - insets.bottom) {
+        if ((insets.top + y + height) > (dragLayer.getBottom() - insets.bottom)) {
             // The container is opening off the screen, so just center it in the drag layer instead.
             mGravity = Gravity.CENTER_VERTICAL;
             // Put the container next to the icon, preferring the right side in ltr (left in rtl).
diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java
index 52356ce..7e9d56d 100644
--- a/src/com/android/launcher3/states/SpringLoadedState.java
+++ b/src/com/android/launcher3/states/SpringLoadedState.java
@@ -53,7 +53,15 @@
 
         float shrunkTop = grid.getWorkspaceSpringLoadShrunkTop();
         float shrunkBottom = grid.getWorkspaceSpringLoadShrunkBottom();
-        float scale = (shrunkBottom - shrunkTop) / ws.getNormalChildHeight();
+        float scale = Math.min((shrunkBottom - shrunkTop) / ws.getNormalChildHeight(), 1f);
+
+        // Reduce scale if next pages would not be visible after scaling the workspace
+        float scaledWorkspaceWidth = ws.getWidth() * scale;
+        float maxAvailableWidth =
+                ws.getWidth() - (2 * grid.getWorkspaceSpringLoadedMinimumNextPageVisible());
+        if (scaledWorkspaceWidth > maxAvailableWidth) {
+            scale *= maxAvailableWidth / scaledWorkspaceWidth;
+        }
 
         float halfHeight = ws.getHeight() / 2;
         float myCenter = ws.getTop() + halfHeight;
diff --git a/src/com/android/launcher3/util/GridOccupancy.java b/src/com/android/launcher3/util/GridOccupancy.java
index 9c752a7..1301460 100644
--- a/src/com/android/launcher3/util/GridOccupancy.java
+++ b/src/com/android/launcher3/util/GridOccupancy.java
@@ -7,7 +7,7 @@
 /**
  * Utility object to manage the occupancy in a grid.
  */
-public class GridOccupancy {
+public class GridOccupancy extends AbsGridOccupancy {
 
     private final int mCountX;
     private final int mCountY;
@@ -30,24 +30,7 @@
      * @return true if a vacant cell was found
      */
     public boolean findVacantCell(int[] vacantOut, int spanX, int spanY) {
-        for (int y = 0; (y + spanY) <= mCountY; y++) {
-            for (int x = 0; (x + spanX) <= mCountX; x++) {
-                boolean available = !cells[x][y];
-                out:
-                for (int i = x; i < x + spanX; i++) {
-                    for (int j = y; j < y + spanY; j++) {
-                        available = available && !cells[i][j];
-                        if (!available) break out;
-                    }
-                }
-                if (available) {
-                    vacantOut[0] = x;
-                    vacantOut[1] = y;
-                    return true;
-                }
-            }
-        }
-        return false;
+        return super.findVacantCell(vacantOut, cells, mCountX, mCountY, spanX, spanY);
     }
 
     public void copyTo(GridOccupancy dest) {
diff --git a/src/com/android/launcher3/util/MultiAdditivePropertyFactory.java b/src/com/android/launcher3/util/MultiAdditivePropertyFactory.java
new file mode 100644
index 0000000..50f7027
--- /dev/null
+++ b/src/com/android/launcher3/util/MultiAdditivePropertyFactory.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util;
+
+import android.util.ArrayMap;
+import android.util.FloatProperty;
+import android.util.Log;
+import android.util.Property;
+import android.view.View;
+
+/**
+ * Allows to combine multiple values set by several sources.
+ *
+ * The various sources are meant to use [set], providing different `setterIndex` params. When it is
+ * not set, 0 is used. This is meant to cover the case multiple animations are going on at the same
+ * time.
+ *
+ * This class behaves similarly to [MultiValueAlpha], but is meant to be more abstract and reusable.
+ * It sets the addition of all values.
+ *
+ * @param <T> Type where to apply the property.
+ */
+public class MultiAdditivePropertyFactory<T extends View> {
+
+    private static final boolean DEBUG = false;
+    private static final String TAG = "MultiAdditivePropertyFactory";
+    private final String mName;
+    private final ArrayMap<Integer, MultiAdditiveProperty> mProperties =
+            new ArrayMap<>();
+
+    // This is an optimization for cases when set is called repeatedly with the same setterIndex.
+    private float mAggregationOfOthers = 0f;
+    private Integer mLastIndexSet = -1;
+    private final Property<View, Float> mProperty;
+
+    public MultiAdditivePropertyFactory(String name, Property<View, Float> property) {
+        mName = name;
+        mProperty = property;
+    }
+
+    /** Returns the [MultiFloatProperty] associated with [inx], creating it if not present. */
+    public MultiAdditiveProperty get(Integer index) {
+        return mProperties.computeIfAbsent(index,
+                (k) -> new MultiAdditiveProperty(index, mName + "_" + index));
+    }
+
+    /**
+     * Each [setValue] will be aggregated with the other properties values created by the
+     * corresponding factory.
+     */
+    class MultiAdditiveProperty extends FloatProperty<T> {
+        private final int mInx;
+        private float mValue = 0f;
+
+        MultiAdditiveProperty(int inx, String name) {
+            super(name);
+            mInx = inx;
+        }
+
+        @Override
+        public void setValue(T obj, float newValue) {
+            if (mLastIndexSet != mInx) {
+                mAggregationOfOthers = 0f;
+                mProperties.forEach((key, property) -> {
+                    if (key != mInx) {
+                        mAggregationOfOthers += property.mValue;
+                    }
+                });
+                mLastIndexSet = mInx;
+            }
+            float lastAggregatedValue = mAggregationOfOthers + newValue;
+            mValue = newValue;
+            apply(obj, lastAggregatedValue);
+
+            if (DEBUG) {
+                Log.d(TAG, "name=" + mName
+                        + " newValue=" + newValue + " mInx=" + mInx
+                        + " aggregated=" + lastAggregatedValue + " others= " + mProperties);
+            }
+        }
+
+        @Override
+        public Float get(T view) {
+            // The scale of the view should match mLastAggregatedValue. Still, if it has been
+            // changed without using this property, it can differ. As this get method is usually
+            // used to set the starting point on an animation, this would result in some jumps
+            // when the view scale is different than the last aggregated value. To stay on the
+            // safe side, let's return the real view scale.
+            return mProperty.get(view);
+        }
+
+        @Override
+        public String toString() {
+            return String.valueOf(mValue);
+        }
+    }
+
+    protected void apply(View view, float value) {
+        mProperty.set(view, value);
+    }
+}
diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
index 1f6551e..130ee3a 100644
--- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
@@ -18,7 +18,6 @@
 
 import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon;
 import static com.android.launcher3.icons.FastBitmapDrawable.getDisabledColorFilter;
-import static com.android.launcher3.widget.WidgetSections.getWidgetSections;
 
 import android.content.Context;
 import android.graphics.Canvas;
@@ -341,8 +340,6 @@
         if (mInfo.pendingItemInfo.widgetCategory == WidgetSections.NO_CATEGORY) {
             return null;
         }
-        Context context = getContext();
-        return context.getDrawable(getWidgetSections(context).get(
-                mInfo.pendingItemInfo.widgetCategory).mSectionDrawable);
+        return mInfo.pendingItemInfo.newIcon(getContext());
     }
 }
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
index 932e06d..b0e2ec1 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
@@ -15,7 +15,6 @@
  */
 package com.android.launcher3.widget.picker;
 
-import static com.android.launcher3.widget.WidgetSections.NO_CATEGORY;
 
 import android.content.Context;
 import android.content.res.Resources;
@@ -43,8 +42,6 @@
 import com.android.launcher3.model.data.PackageItemInfo;
 import com.android.launcher3.util.PluralMessageFormat;
 import com.android.launcher3.views.ActivityContext;
-import com.android.launcher3.widget.WidgetSections;
-import com.android.launcher3.widget.WidgetSections.WidgetSection;
 import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
 import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
 
@@ -177,13 +174,7 @@
 
     private void setIcon(PackageItemInfo info) {
         Drawable icon;
-        if (info.widgetCategory == NO_CATEGORY) {
-            icon = info.newIcon(getContext());
-        } else {
-            WidgetSection widgetSection = WidgetSections.getWidgetSections(getContext())
-                    .get(info.widgetCategory);
-            icon = getContext().getDrawable(widgetSection.mSectionDrawable);
-        }
+        icon = info.newIcon(getContext());
         applyDrawables(icon);
         mIconDrawable = icon;
         if (mIconDrawable != null) {
diff --git a/src_shortcuts_overrides/com/android/launcher3/util/AbsGridOccupancy.java b/src_shortcuts_overrides/com/android/launcher3/util/AbsGridOccupancy.java
new file mode 100644
index 0000000..968b281
--- /dev/null
+++ b/src_shortcuts_overrides/com/android/launcher3/util/AbsGridOccupancy.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util;
+
+/**
+ * Defines method to find the next vacant cell on a grid.
+ * This uses the default top-down, left-right approach and can be over-written through
+ * code swaps in different launchers.
+ */
+public abstract class AbsGridOccupancy {
+    /**
+     * Find the first vacant cell, if there is one.
+     *
+     * @param vacantOut Holds the x and y coordinate of the vacant cell
+     * @param spanX Horizontal cell span.
+     * @param spanY Vertical cell span.
+     *
+     * @return true if a vacant cell was found
+     */
+    protected boolean findVacantCell(int[] vacantOut, boolean[][] cells, int countX, int countY,
+            int spanX, int spanY) {
+        for (int y = 0; (y + spanY) <= countY; y++) {
+            for (int x = 0; (x + spanX) <= countX; x++) {
+                boolean available = !cells[x][y];
+                out:
+                for (int i = x; i < x + spanX; i++) {
+                    for (int j = y; j < y + spanY; j++) {
+                        available = available && !cells[i][j];
+                        if (!available) break out;
+                    }
+                }
+                if (available) {
+                    vacantOut[0] = x;
+                    vacantOut[1] = y;
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+}
diff --git a/tests/Android.bp b/tests/Android.bp
index 7542d04..54cded0 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -37,6 +37,7 @@
       "src/com/android/launcher3/util/WidgetUtils.java",
       "src/com/android/launcher3/util/rule/FailureWatcher.java",
       "src/com/android/launcher3/util/rule/LauncherActivityRule.java",
+      "src/com/android/launcher3/util/rule/SamplerRule.java",
       "src/com/android/launcher3/util/rule/ScreenRecordRule.java",
       "src/com/android/launcher3/util/rule/ShellCommandRule.java",
       "src/com/android/launcher3/util/rule/SimpleActivityRule.java",
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 136f115..7080c85 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -68,6 +68,7 @@
 import com.android.launcher3.util.WidgetUtils;
 import com.android.launcher3.util.rule.FailureWatcher;
 import com.android.launcher3.util.rule.LauncherActivityRule;
+import com.android.launcher3.util.rule.SamplerRule;
 import com.android.launcher3.util.rule.ScreenRecordRule;
 import com.android.launcher3.util.rule.ShellCommandRule;
 import com.android.launcher3.util.rule.TestStabilityRule;
@@ -227,7 +228,8 @@
 
     @Rule
     public TestRule mOrderSensitiveRules = RuleChain
-            .outerRule(new TestStabilityRule())
+            .outerRule(new SamplerRule())
+            .around(new TestStabilityRule())
             .around(mActivityMonitor)
             .around(getRulesInsideActivityMonitor());
 
diff --git a/tests/src/com/android/launcher3/util/MultiAdditivePropertyTest.kt b/tests/src/com/android/launcher3/util/MultiAdditivePropertyTest.kt
new file mode 100644
index 0000000..309d055
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/MultiAdditivePropertyTest.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** Unit tests for [MultiAdditivePropertyFactory] */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MultiAdditivePropertyTest {
+
+    private val received = mutableListOf<Float>()
+
+    private val factory =
+        object : MultiAdditivePropertyFactory<View?>("Test", View.TRANSLATION_X) {
+            override fun apply(obj: View?, value: Float) {
+                received.add(value)
+            }
+        }
+
+    private val p1 = factory.get(1)
+    private val p2 = factory.get(2)
+    private val p3 = factory.get(3)
+
+    @Test
+    fun set_sameIndexes_allApplied() {
+        val v1 = 50f
+        val v2 = 100f
+        p1.set(null, v1)
+        p1.set(null, v1)
+        p1.set(null, v2)
+
+        assertThat(received).containsExactly(v1, v1, v2)
+    }
+
+    @Test
+    fun set_differentIndexes_aggregationApplied() {
+        val v1 = 50f
+        val v2 = 100f
+        val v3 = 150f
+        p1.set(null, v1)
+        p2.set(null, v2)
+        p3.set(null, v3)
+
+        assertThat(received).containsExactly(v1, v1 + v2, v1 + v2 + v3)
+    }
+}
diff --git a/tests/src/com/android/launcher3/util/MultiScalePropertyTest.kt b/tests/src/com/android/launcher3/util/MultiScalePropertyTest.kt
index 6099987..7d92214 100644
--- a/tests/src/com/android/launcher3/util/MultiScalePropertyTest.kt
+++ b/tests/src/com/android/launcher3/util/MultiScalePropertyTest.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.launcher3.util
 
 import android.view.View
diff --git a/tests/src/com/android/launcher3/util/rule/SamplerRule.java b/tests/src/com/android/launcher3/util/rule/SamplerRule.java
new file mode 100644
index 0000000..6125f2a
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/rule/SamplerRule.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util.rule;
+
+import android.os.SystemClock;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * A rule that generates a file that helps diagnosing cases when the test process was terminated
+ * because the test execution took too long, and tests that ran for too long even without being
+ * terminated. If the process was terminated or the test was long, the test leaves an artifact with
+ * stack traces of all threads, every SAMPLE_INTERVAL_MS. This will help understanding where we
+ * stuck.
+ */
+public class SamplerRule implements TestRule {
+    private static final int TOO_LONG_TEST_MS = 180000;
+    private static final int SAMPLE_INTERVAL_MS = 3000;
+
+    public static Thread startThread(Description description) {
+        Thread thread =
+                new Thread() {
+                    @Override
+                    public void run() {
+                        // Write all-threads stack stace every SAMPLE_INTERVAL_MS while the test
+                        // is running.
+                        // After the test finishes, delete that file. If the test process is
+                        // terminated due to timeout, the trace file won't be deleted.
+                        final File file = getFile();
+
+                        final long startTime = SystemClock.elapsedRealtime();
+                        try (OutputStreamWriter outputStreamWriter =
+                                     new OutputStreamWriter(
+                                             new BufferedOutputStream(
+                                                     new FileOutputStream(file)))) {
+                            writeSamples(outputStreamWriter);
+                        } catch (IOException | InterruptedException e) {
+                            // Simply suppressing the exceptions, nothing to do here.
+                        } finally {
+                            // If the process is not killed, then there was no test timeout, and
+                            // we are not interested in the trace file, unless the test ran too
+                            // long.
+                            if (SystemClock.elapsedRealtime() - startTime < TOO_LONG_TEST_MS) {
+                                file.delete();
+                            }
+                        }
+                    }
+
+                    private File getFile() {
+                        final String strDate = new SimpleDateFormat("HH:mm:ss").format(new Date());
+
+                        final String descStr = description.getTestClass().getSimpleName() + "."
+                                + description.getMethodName();
+                        return artifactFile(
+                                "ThreadStackSamples-" + strDate + "-" + descStr + ".txt");
+                    }
+
+                    private void writeSamples(OutputStreamWriter writer)
+                            throws IOException, InterruptedException {
+                        int count = 0;
+                        while (true) {
+                            writer.write(
+                                    "#"
+                                            + (count++)
+                                            + " =============================================\r\n");
+                            for (StackTraceElement[] stack : getAllStackTraces().values()) {
+                                writer.write("---------------------\r\n");
+                                for (StackTraceElement frame : stack) {
+                                    writer.write(frame.toString() + "\r\n");
+                                }
+                            }
+                            writer.flush();
+
+                            sleep(SAMPLE_INTERVAL_MS);
+                        }
+                    }
+                };
+
+        thread.start();
+        return thread;
+    }
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                final Thread traceThread = startThread(description);
+                try {
+                    base.evaluate();
+                } finally {
+                    traceThread.interrupt();
+                    traceThread.join();
+                }
+            }
+        };
+    }
+
+    private static File artifactFile(String fileName) {
+        return new File(
+                InstrumentationRegistry.getInstrumentation().getTargetContext().getFilesDir(),
+                fileName);
+    }
+}