[automerger skipped] Import translations. DO NOT MERGE ANYWHERE am: ec17e24889 -s ours am: 9976faf55a -s ours

am skip reason: subject contains skip directive

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/19118362

Change-Id: I8895e003db2c9eb43b08f5f7a3cb74dbe1565f94
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/Android.bp b/Android.bp
index 0a55675..4adbf53 100644
--- a/Android.bp
+++ b/Android.bp
@@ -313,3 +313,138 @@
         baseline_filename: "lint-baseline-launcher3.xml",
     },
 }
+
+// Build rule for Launcher3 Go app for Android Go devices.
+android_app {
+    name: "Launcher3Go",
+
+    static_libs: ["Launcher3CommonDepsLib"],
+
+    srcs: [
+        "src/**/*.java",
+        "src_ui_overrides/**/*.java",
+        "go/src/**/*.java",
+    ],
+
+    resource_dirs: ["go/res"],
+
+    optimize: {
+        proguard_flags_files: ["proguard.flags"],
+    },
+
+    sdk_version: "current",
+    min_sdk_version: "current",
+    target_sdk_version: "current",
+    privileged: true,
+    system_ext_specific: true,
+    overrides: [
+        "Home",
+        "Launcher2",
+        "Launcher3",
+        "Launcher3QuickStep",
+    ],
+    required: ["privapp_whitelist_com.android.launcher3"],
+
+    additional_manifests: [
+        "AndroidManifest.xml",
+        "AndroidManifest-common.xml",
+    ],
+
+    manifest: "go/AndroidManifest.xml",
+    jacoco: {
+        include_filter: ["com.android.launcher3.*"],
+    }
+
+}
+
+// Build rule for Quickstep app.
+android_app {
+    name: "Launcher3QuickStep",
+
+    static_libs: ["Launcher3QuickStepLib"],
+    optimize: {
+        enabled: false,
+    },
+
+    platform_apis: true,
+    min_sdk_version: "current",
+    target_sdk_version: "current",
+
+    privileged: true,
+    system_ext_specific: true,
+    overrides: [
+        "Home",
+        "Launcher2",
+        "Launcher3",
+    ],
+    required: ["privapp_whitelist_com.android.launcher3"],
+
+    resource_dirs: ["quickstep/res"],
+
+    additional_manifests: [
+        "quickstep/AndroidManifest-launcher.xml",
+        "AndroidManifest-common.xml",
+    ],
+
+    manifest: "quickstep/AndroidManifest.xml",
+    jacoco: {
+        include_filter: ["com.android.launcher3.*"],
+    }
+
+}
+
+// Build rule for Launcher3 Go app with quickstep for Android Go devices.
+android_app {
+    name: "Launcher3QuickStepGo",
+
+    static_libs: [
+        "SystemUI-statsd",
+        "SystemUISharedLib",
+        "LauncherGoResLib",
+    ],
+
+    platform_apis: true,
+    min_sdk_version: "current",
+    target_sdk_version: "current",
+
+    srcs: [
+        "src/**/*.java",
+        "quickstep/src/**/*.java",
+        "go/src/**/*.java",
+        "go/quickstep/src/**/*.java",
+    ],
+
+    resource_dirs: [
+        "go/quickstep/res",
+        "go/res",
+        "quickstep/res",
+    ],
+
+    optimize: {
+        proguard_flags_files: ["proguard.flags"],
+        enabled: true,
+    },
+
+    privileged: true,
+    system_ext_specific: true,
+    overrides: [
+        "Home",
+        "Launcher2",
+        "Launcher3",
+        "Launcher3QuickStep",
+    ],
+    required: ["privapp_whitelist_com.android.launcher3"],
+
+    additional_manifests: [
+        "go/AndroidManifest.xml",
+        "go/AndroidManifest-launcher.xml",
+        "AndroidManifest-common.xml",
+    ],
+
+    manifest: "quickstep/AndroidManifest.xml",
+    jacoco: {
+        include_filter: ["com.android.launcher3.*"],
+    }
+
+}
+
diff --git a/Android.mk b/Android.mk
deleted file mode 100644
index 1bc8b28..0000000
--- a/Android.mk
+++ /dev/null
@@ -1,147 +0,0 @@
-#
-# Copyright (C) 2013 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.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-#
-# Build rule for Launcher3 Go app for Android Go devices.
-#
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_MODULE_TAGS := optional
-LOCAL_STATIC_ANDROID_LIBRARIES := Launcher3CommonDepsLib
-
-LOCAL_SRC_FILES := \
-    $(call all-java-files-under, src) \
-    $(call all-java-files-under, src_ui_overrides) \
-    $(call all-java-files-under, go/src)
-
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/go/res
-
-LOCAL_PROGUARD_FLAG_FILES := proguard.flags
-
-LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 26
-LOCAL_PACKAGE_NAME := Launcher3Go
-LOCAL_PRIVILEGED_MODULE := true
-LOCAL_SYSTEM_EXT_MODULE := true
-LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 Launcher3QuickStep
-LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3
-
-LOCAL_FULL_LIBS_MANIFEST_FILES := \
-    $(LOCAL_PATH)/AndroidManifest.xml \
-    $(LOCAL_PATH)/AndroidManifest-common.xml
-
-LOCAL_MANIFEST_FILE := go/AndroidManifest.xml
-LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.launcher3.*
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_LICENSE_PACKAGE_NAME := Android Launcher3
-LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
-include $(BUILD_PACKAGE)
-
-#
-# Build rule for Quickstep app.
-#
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_STATIC_ANDROID_LIBRARIES := Launcher3QuickStepLib
-LOCAL_PROGUARD_ENABLED := disabled
-
-ifneq (,$(wildcard frameworks/base))
-  LOCAL_PRIVATE_PLATFORM_APIS := true
-else
-  LOCAL_SDK_VERSION := system_current
-  LOCAL_MIN_SDK_VERSION := 26
-endif
-LOCAL_PACKAGE_NAME := Launcher3QuickStep
-LOCAL_PRIVILEGED_MODULE := true
-LOCAL_SYSTEM_EXT_MODULE := true
-LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3
-LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3
-
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/quickstep/res
-
-LOCAL_FULL_LIBS_MANIFEST_FILES := \
-    $(LOCAL_PATH)/quickstep/AndroidManifest-launcher.xml \
-    $(LOCAL_PATH)/AndroidManifest-common.xml
-
-LOCAL_MANIFEST_FILE := quickstep/AndroidManifest.xml
-LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.launcher3.*
-
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_LICENSE_PACKAGE_NAME := Android Launcher3
-LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
-include $(BUILD_PACKAGE)
-
-
-#
-# Build rule for Launcher3 Go app with quickstep for Android Go devices.
-#
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    SystemUI-statsd \
-    SystemUISharedLib
-ifneq (,$(wildcard frameworks/base))
-  LOCAL_PRIVATE_PLATFORM_APIS := true
-else
-  LOCAL_SDK_VERSION := system_current
-  LOCAL_MIN_SDK_VERSION := 26
-endif
-LOCAL_STATIC_ANDROID_LIBRARIES := LauncherGoResLib
-
-LOCAL_SRC_FILES := \
-    $(call all-java-files-under, src) \
-    $(call all-java-files-under, quickstep/src) \
-    $(call all-java-files-under, go/src) \
-    $(call all-java-files-under, go/quickstep/src)
-
-LOCAL_RESOURCE_DIR := \
-    $(LOCAL_PATH)/go/quickstep/res \
-    $(LOCAL_PATH)/go/res \
-    $(LOCAL_PATH)/quickstep/res
-
-LOCAL_PROGUARD_FLAG_FILES := proguard.flags
-LOCAL_PROGUARD_ENABLED := full
-
-LOCAL_PACKAGE_NAME := Launcher3QuickStepGo
-LOCAL_PRIVILEGED_MODULE := true
-LOCAL_SYSTEM_EXT_MODULE := true
-LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 Launcher3QuickStep
-LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3
-
-LOCAL_FULL_LIBS_MANIFEST_FILES := \
-    $(LOCAL_PATH)/go/AndroidManifest.xml \
-    $(LOCAL_PATH)/go/AndroidManifest-launcher.xml \
-    $(LOCAL_PATH)/AndroidManifest-common.xml
-
-LOCAL_MANIFEST_FILE := quickstep/AndroidManifest.xml
-LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.launcher3.*
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_LICENSE_PACKAGE_NAME := Android Launcher3
-LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
-include $(BUILD_PACKAGE)
-
-
-# ==================================================
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/OWNERS b/OWNERS
index 7f98ea6..560b562 100644
--- a/OWNERS
+++ b/OWNERS
@@ -7,13 +7,6 @@
 alexchau@google.com
 andraskloczl@google.com
 patmanning@google.com
-petrcermak@google.com
-pbdr@google.com
-kideckel@google.com
-stevenckng@google.com
-ydixit@google.com
-boadway@google.com
-alinazaidi@google.com
 adamcohen@google.com
 hyunyoungs@google.com
 mrcasey@google.com
@@ -24,10 +17,8 @@
 zakcohen@google.com
 santie@google.com
 vadimt@google.com
-mett@google.com
 jonmiranda@google.com
 pinyaoting@google.com
-sfufa@google.com
 gwasserman@google.com
 jamesoleary@google.com
 joshtrask@google.com
@@ -37,8 +28,6 @@
 tracyzhou@google.com
 peanutbutter@google.com
 xuqiu@google.com
-sreyasr@google.com
-thiruram@google.com
 brianji@google.com
 
 per-file FeatureFlags.java, globs = set noparent
diff --git a/quickstep/Android.bp b/quickstep/Android.bp
index 70b1438..f739f81 100644
--- a/quickstep/Android.bp
+++ b/quickstep/Android.bp
@@ -26,7 +26,7 @@
 filegroup {
     name: "launcher3-quickstep-tests-src",
     path: "tests",
-    srcs: ["tests/src/**/*.java"],
+    srcs: ["tests/src/**/*.java", "tests/src/**/*.kt"],
 }
 
 filegroup {
diff --git a/quickstep/res/drawable/split_instructions_background.xml b/quickstep/res/drawable/split_instructions_background.xml
new file mode 100644
index 0000000..6d0e7db
--- /dev/null
+++ b/quickstep/res/drawable/split_instructions_background.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:shape="rectangle">
+    <solid android:color="?androidprv:attr/colorAccentPrimary" />
+    <corners android:radius="@dimen/split_instructions_radius" />
+</shape>
\ No newline at end of file
diff --git a/quickstep/res/layout/split_instructions_view.xml b/quickstep/res/layout/split_instructions_view.xml
new file mode 100644
index 0000000..91fb05c
--- /dev/null
+++ b/quickstep/res/layout/split_instructions_view.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<com.android.quickstep.views.SplitInstructionsView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:background="@drawable/split_instructions_background"
+    android:paddingRight="@dimen/split_instructions_horizontal_padding"
+    android:paddingLeft="@dimen/split_instructions_horizontal_padding"
+    android:paddingTop="@dimen/split_instructions_vertical_padding"
+    android:paddingBottom="@dimen/split_instructions_vertical_padding"
+    android:elevation="@dimen/split_instructions_elevation"
+    android:visibility="gone">
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/split_instructions_text"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:gravity="center"
+        android:textColor="?androidprv:attr/textColorOnAccent"
+        android:text="@string/toast_split_select_app" />
+</com.android.quickstep.views.SplitInstructionsView>
\ No newline at end of file
diff --git a/quickstep/res/values-land/dimens.xml b/quickstep/res/values-land/dimens.xml
index f233bde..732f511 100644
--- a/quickstep/res/values-land/dimens.xml
+++ b/quickstep/res/values-land/dimens.xml
@@ -15,7 +15,8 @@
      limitations under the License.
 -->
 <resources>
-    <dimen name="overview_task_margin">8dp</dimen>
+    <!--  Overview actions  -->
+    <dimen name="overview_actions_top_margin">12dp</dimen>
 
     <!-- Tips Gesture Tutorial -->
     <dimen name="gesture_tutorial_feedback_margin_start_end">126dp</dimen>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 3072a3e..0fd3c4a 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -239,7 +239,7 @@
     <dimen name="navigation_key_padding">0dp</dimen>
 
     <!-- Floating rotation button -->
-    <dimen name="floating_rotation_button_diameter">40dp</dimen>
+    <dimen name="floating_rotation_button_diameter">52dp</dimen>
     <dimen name="floating_rotation_button_min_margin">20dp</dimen>
     <dimen name="floating_rotation_button_taskbar_left_margin">20dp</dimen>
     <dimen name="floating_rotation_button_taskbar_bottom_margin">10dp</dimen>
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 2239102..e21dcba 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -20,6 +20,8 @@
 import static com.android.launcher3.LauncherState.FLAG_HIDE_BACK_BUTTON;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.NO_OFFSET;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE;
 import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
@@ -70,6 +72,7 @@
 import com.android.launcher3.util.DisplayController.NavigationMode;
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.ObjectWrapper;
+import com.android.launcher3.util.PendingSplitSelectInfo;
 import com.android.launcher3.util.RunnableList;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
 import com.android.launcher3.util.UiThreadHelper;
@@ -91,7 +94,10 @@
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.unfold.UnfoldTransitionFactory;
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
+import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig;
 import com.android.systemui.unfold.config.UnfoldTransitionConfig;
+import com.android.systemui.unfold.system.ActivityManagerActivityTypeProvider;
+import com.android.systemui.unfold.system.DeviceStateManagerFoldProvider;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -127,9 +133,19 @@
     private @Nullable UnfoldTransitionProgressProvider mUnfoldTransitionProgressProvider;
     private @Nullable LauncherUnfoldAnimationController mLauncherUnfoldAnimationController;
 
+    /**
+     * If Launcher restarted while in the middle of an Overview split select, it needs this data to
+     * recover. In all other cases this will remain null.
+     */
+    private PendingSplitSelectInfo mPendingSplitSelectInfo = null;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        if (savedInstanceState != null) {
+            mPendingSplitSelectInfo = ObjectWrapper.unwrap(
+                    savedInstanceState.getIBinder(PENDING_SPLIT_SELECT_INFO));
+        }
         addMultiWindowModeChangedListener(mDepthController);
         initUnfoldTransitionProgressProvider();
     }
@@ -343,15 +359,17 @@
     }
 
     private void initUnfoldTransitionProgressProvider() {
-        final UnfoldTransitionConfig config = UnfoldTransitionFactory.createConfig(this);
+        final UnfoldTransitionConfig config = new ResourceUnfoldTransitionConfig();
         if (config.isEnabled()) {
             mUnfoldTransitionProgressProvider =
                     UnfoldTransitionFactory.createUnfoldTransitionProgressProvider(
-                            this,
+                            /* context= */ this,
                             config,
                             ProxyScreenStatusProvider.INSTANCE,
-                            getSystemService(DeviceStateManager.class),
-                            getSystemService(ActivityManager.class),
+                            new DeviceStateManagerFoldProvider(
+                                    getSystemService(DeviceStateManager.class), /* context */this),
+                            new ActivityManagerActivityTypeProvider(
+                                    getSystemService(ActivityManager.class)),
                             getSystemService(SensorManager.class),
                             getMainThreadHandler(),
                             getMainExecutor(),
@@ -638,4 +656,53 @@
             mDepthController.dump(prefix, writer);
         }
     }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+
+        // If Launcher shuts downs during split select, we save some extra data in the recovery
+        // bundle to allow graceful recovery. The normal LauncherState restore mechanism doesn't
+        // work in this case because restoring straight to OverviewSplitSelect without staging data,
+        // or before the tasks themselves have loaded into Overview, causes a crash. So we tell
+        // Launcher to first restore into Overview state, wait for the relevant tasks and icons to
+        // load in, and then proceed to OverviewSplitSelect.
+        if (isInState(OVERVIEW_SPLIT_SELECT)) {
+            SplitSelectStateController splitSelectStateController =
+                    ((RecentsView) getOverviewPanel()).getSplitPlaceholder();
+            // Launcher will restart in Overview and then transition to OverviewSplitSelect.
+            outState.putIBinder(PENDING_SPLIT_SELECT_INFO, ObjectWrapper.wrap(
+                    new PendingSplitSelectInfo(
+                            splitSelectStateController.getInitialTaskId(),
+                            splitSelectStateController.getActiveSplitStagePosition()
+                    )
+            ));
+            outState.putInt(RUNTIME_STATE, OVERVIEW.ordinal);
+        }
+    }
+
+    /**
+     * When Launcher restarts, it sometimes needs to recover to a split selection state.
+     * This function checks if such a recovery is needed.
+     * @return a boolean representing whether the launcher is waiting to recover to
+     * OverviewSplitSelect state.
+     */
+    public boolean hasPendingSplitSelectInfo() {
+        return mPendingSplitSelectInfo != null;
+    }
+
+    /**
+     * See {@link #hasPendingSplitSelectInfo()}
+     */
+    public @Nullable PendingSplitSelectInfo getPendingSplitSelectInfo() {
+        return mPendingSplitSelectInfo;
+    }
+
+    /**
+     * When the launcher has successfully recovered to OverviewSplitSelect state, this function
+     * deletes the recovery data, returning it to a null state.
+     */
+    public void finishSplitSelectRecovery() {
+        mPendingSplitSelectInfo = null;
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index e1a3b72..b20752d 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -31,7 +31,6 @@
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.Utilities.mapBoundToRange;
-import static com.android.launcher3.Utilities.postAsyncCallback;
 import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
 import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_1_5;
@@ -184,6 +183,10 @@
     public static final int SPLIT_DIVIDER_ANIM_DURATION = 100;
 
     public static final int CONTENT_ALPHA_DURATION = 217;
+    public static final int TASKBAR_TO_APP_DURATION = 600;
+    // TODO(b/236145847): Tune TASKBAR_TO_HOME_DURATION to 383 after conflict with unlock animation
+    // is solved.
+    public static final int TASKBAR_TO_HOME_DURATION = 300;
     protected static final int CONTENT_SCALE_DURATION = 350;
     protected static final int CONTENT_SCRIM_DURATION = 350;
 
@@ -527,7 +530,15 @@
             workspace.forEachVisiblePage(
                     view -> viewsToAnimate.add(((CellLayout) view).getShortcutsAndWidgets()));
 
-            viewsToAnimate.add(mLauncher.getHotseat());
+            // Do not scale hotseat as a whole when taskbar is present, and scale QSB only if it's
+            // not inline.
+            if (mDeviceProfile.isTaskbarPresent) {
+                if (!mDeviceProfile.isQsbInline) {
+                    viewsToAnimate.add(mLauncher.getHotseat().getQsb());
+                }
+            } else {
+                viewsToAnimate.add(mLauncher.getHotseat());
+            }
 
             viewsToAnimate.forEach(view -> {
                 view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
diff --git a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
index 0284ae4..f42b39f 100644
--- a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
@@ -21,7 +21,6 @@
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.graphics.Canvas;
-import android.graphics.Rect;
 import android.graphics.Typeface;
 import android.os.Build;
 import android.text.Layout;
@@ -33,7 +32,6 @@
 import androidx.annotation.ColorInt;
 import androidx.core.content.ContextCompat;
 
-import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.FloatingHeaderRow;
 import com.android.launcher3.allapps.FloatingHeaderView;
@@ -239,12 +237,6 @@
     }
 
     @Override
-    public void setInsets(Rect insets, DeviceProfile grid) {
-        int leftRightPadding = grid.allAppsLeftRightPadding;
-        setPadding(leftRightPadding, getPaddingTop(), leftRightPadding, getPaddingBottom());
-    }
-
-    @Override
     public void setVerticalScroll(int scroll, boolean isScrolledOut) {
         setTranslationY(scroll);
         mIsScrolledOut = isScrolledOut;
diff --git a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
index 1dec737..351a3bc 100644
--- a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -19,7 +19,6 @@
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.graphics.Canvas;
-import android.graphics.Rect;
 import android.os.Build;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
@@ -252,12 +251,6 @@
     }
 
     @Override
-    public void setInsets(Rect insets, DeviceProfile grid) {
-        int leftRightPadding = grid.allAppsLeftRightPadding;
-        setPadding(leftRightPadding, getPaddingTop(), leftRightPadding, getPaddingBottom());
-    }
-
-    @Override
     public Class<PredictionRowView> getTypeClass() {
         return PredictionRowView.class;
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index ca30e72..6df31e5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -171,7 +171,9 @@
                 isResumed,
                 fromInit,
                 /* startAnimation= */ true,
-                QuickstepTransitionManager.CONTENT_ALPHA_DURATION);
+                !isResumed
+                        ? QuickstepTransitionManager.TASKBAR_TO_APP_DURATION
+                        : QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION);
     }
 
     @Nullable
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 5d576f7..17da0d9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -35,6 +35,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
 import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_REGION;
 
 import android.animation.ArgbEvaluator;
@@ -105,8 +106,7 @@
     private static final int FLAG_DISABLE_BACK = 1 << 9;
     private static final int FLAG_NOTIFICATION_SHADE_EXPANDED = 1 << 10;
     private static final int FLAG_SCREEN_PINNING_ACTIVE = 1 << 11;
-
-    private static final int MASK_IME_SWITCHER_VISIBLE = FLAG_SWITCHER_SUPPORTED | FLAG_IME_VISIBLE;
+    private static final int FLAG_VOICE_INTERACTION_WINDOW_SHOWING = 1 << 12;
 
     private static final String NAV_BUTTONS_SEPARATE_WINDOW_TITLE = "Taskbar Nav Buttons";
 
@@ -189,7 +189,7 @@
                     isThreeButtonNav ? mStartContextualContainer : mEndContextualContainer,
                     mControllers.navButtonController, R.id.ime_switcher);
             mPropertyHolders.add(new StatePropertyHolder(imeSwitcherButton,
-                    flags -> ((flags & MASK_IME_SWITCHER_VISIBLE) == MASK_IME_SWITCHER_VISIBLE)
+                    flags -> ((flags & FLAG_SWITCHER_SUPPORTED) != 0)
                             && ((flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0)));
         }
 
@@ -207,9 +207,12 @@
         boolean isInKidsMode = mContext.isNavBarKidsModeActive();
         boolean alwaysShowButtons = isThreeButtonNav || isInSetup;
 
-        // Make sure to remove nav bar buttons translation when notification shade is expanded or
-        // IME is showing (add separate translation for IME).
-        int flagsToRemoveTranslation = FLAG_NOTIFICATION_SHADE_EXPANDED | FLAG_IME_VISIBLE;
+        // Make sure to remove nav bar buttons translation when any of the following occur:
+        // - Notification shade is expanded
+        // - IME is showing (add separate translation for IME)
+        // - VoiceInteractionWindow (assistant) is showing
+        int flagsToRemoveTranslation = FLAG_NOTIFICATION_SHADE_EXPANDED | FLAG_IME_VISIBLE
+                | FLAG_VOICE_INTERACTION_WINDOW_SHOWING;
         mPropertyHolders.add(new StatePropertyHolder(mNavButtonInAppDisplayProgressForSysui,
                 flags -> (flags & flagsToRemoveTranslation) != 0, AnimatedFloat.VALUE,
                 1, 0));
@@ -415,7 +418,7 @@
                     return recentsCoords;
                 }, new Handler());
         recentsButton.setOnClickListener(v -> {
-            navButtonController.onButtonClick(BUTTON_RECENTS);
+            navButtonController.onButtonClick(BUTTON_RECENTS, v);
             mHitboxExtender.onRecentsButtonClicked();
         });
         mPropertyHolders.add(new StatePropertyHolder(recentsButton,
@@ -443,6 +446,8 @@
                 | SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
         boolean isNotificationShadeExpanded = (sysUiStateFlags & shadeExpandedFlags) != 0;
         boolean isScreenPinningActive = (sysUiStateFlags & SYSUI_STATE_SCREEN_PINNING) != 0;
+        boolean isVoiceInteractionWindowShowing =
+                (sysUiStateFlags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0;
 
         // TODO(b/202218289) we're getting IME as not visible on lockscreen from system
         updateStateForFlag(FLAG_IME_VISIBLE, isImeVisible);
@@ -453,6 +458,7 @@
         updateStateForFlag(FLAG_DISABLE_BACK, isBackDisabled);
         updateStateForFlag(FLAG_NOTIFICATION_SHADE_EXPANDED, isNotificationShadeExpanded);
         updateStateForFlag(FLAG_SCREEN_PINNING_ACTIVE, isScreenPinningActive);
+        updateStateForFlag(FLAG_VOICE_INTERACTION_WINDOW_SHOWING, isVoiceInteractionWindowShowing);
 
         if (mA11yButton != null) {
             // Only used in 3 button
@@ -499,6 +505,13 @@
     }
 
     /**
+     * Returns true if IME switcher is visible
+     */
+    public boolean isImeSwitcherVisible() {
+        return (mState & FLAG_SWITCHER_SUPPORTED) != 0;
+    }
+
+    /**
      * Returns true if the home button is disabled
      */
     public boolean isHomeDisabled() {
@@ -626,9 +639,9 @@
         buttonView.setImageResource(drawableId);
         buttonView.setContentDescription(parent.getContext().getString(
                 navButtonController.getButtonContentDescription(buttonType)));
-        buttonView.setOnClickListener(view -> navButtonController.onButtonClick(buttonType));
+        buttonView.setOnClickListener(view -> navButtonController.onButtonClick(buttonType, view));
         buttonView.setOnLongClickListener(view ->
-                navButtonController.onButtonLongClick(buttonType));
+                navButtonController.onButtonLongClick(buttonType, view));
         return buttonView;
     }
 
@@ -750,6 +763,8 @@
         appendFlag(str, flags, FLAG_NOTIFICATION_SHADE_EXPANDED,
                 "FLAG_NOTIFICATION_SHADE_EXPANDED");
         appendFlag(str, flags, FLAG_SCREEN_PINNING_ACTIVE, "FLAG_SCREEN_PINNING_ACTIVE");
+        appendFlag(str, flags, FLAG_VOICE_INTERACTION_WINDOW_SHOWING,
+                "FLAG_VOICE_INTERACTION_WINDOW_SHOWING");
         return str.toString();
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
index b797807..f472427 100644
--- a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
@@ -43,7 +43,8 @@
 
     public static final int ALPHA_INDEX_STASHED = 0;
     public static final int ALPHA_INDEX_HOME_DISABLED = 1;
-    private static final int NUM_ALPHA_CHANNELS = 2;
+    public static final int ALPHA_INDEX_ASSISTANT_INVOKED = 2;
+    private static final int NUM_ALPHA_CHANNELS = 3;
 
     /**
      * The SharedPreferences key for whether the stashed handle region is dark.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index dc2e3b6..439490e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -27,6 +27,7 @@
 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.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
 
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
@@ -200,7 +201,8 @@
                 new TaskbarPopupController(this),
                 new TaskbarForceVisibleImmersiveController(this),
                 new TaskbarAllAppsController(this, dp),
-                new TaskbarInsetsController(this));
+                new TaskbarInsetsController(this),
+                new VoiceInteractionWindowController(this));
     }
 
     public void init(@NonNull TaskbarSharedState sharedState) {
@@ -246,14 +248,23 @@
         return super.getStatsLogManager();
     }
 
-    /** Creates LayoutParams for adding a view directly to WindowManager as a new window */
+    /** @see #createDefaultWindowLayoutParams(int) */
     public WindowManager.LayoutParams createDefaultWindowLayoutParams() {
+        return createDefaultWindowLayoutParams(TYPE_NAVIGATION_BAR_PANEL);
+    }
+
+    /**
+     * Creates LayoutParams for adding a view directly to WindowManager as a new window.
+     * @param type The window type to pass to the created WindowManager.LayoutParams.
+     */
+    public WindowManager.LayoutParams createDefaultWindowLayoutParams(int type) {
         WindowManager.LayoutParams windowLayoutParams = new WindowManager.LayoutParams(
                 MATCH_PARENT,
                 mLastRequestedNonFullscreenHeight,
-                TYPE_NAVIGATION_BAR_PANEL,
+                type,
                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                        | WindowManager.LayoutParams.FLAG_SLIPPERY,
+                        | WindowManager.LayoutParams.FLAG_SLIPPERY
+                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
                 PixelFormat.TRANSLUCENT);
         windowLayoutParams.setTitle(WINDOW_TITLE);
         windowLayoutParams.packageName = getPackageName();
@@ -453,6 +464,8 @@
                 fromInit);
         mControllers.taskbarViewController.setImeIsVisible(
                 mControllers.navbarButtonsViewController.isImeVisible());
+        mControllers.taskbarViewController.setIsImeSwitcherVisible(
+                mControllers.navbarButtonsViewController.isImeSwitcherVisible());
         int shadeExpandedFlags = SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
                 | SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
         onNotificationShadeExpandChanged((systemUiStateFlags & shadeExpandedFlags) != 0, fromInit);
@@ -468,6 +481,8 @@
                 fromInit);
         mControllers.navButtonController.updateSysuiFlags(systemUiStateFlags);
         mControllers.taskbarForceVisibleImmersiveController.updateSysuiFlags(systemUiStateFlags);
+        mControllers.voiceInteractionWindowController.setIsVoiceInteractionWindowVisible(
+                (systemUiStateFlags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0, fromInit);
     }
 
     /**
@@ -612,7 +627,9 @@
 
     /** Removes the given view from WindowManager. See {@link #addWindowView}. */
     public void removeWindowView(View view) {
-        mWindowManager.removeViewImmediate(view);
+        if (view.isAttachedToWindow()) {
+            mWindowManager.removeViewImmediate(view);
+        }
     }
 
     protected void onTaskbarIconClicked(View view) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
index 449e0a7..d7b50b0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -52,6 +52,7 @@
     public final TaskbarForceVisibleImmersiveController taskbarForceVisibleImmersiveController;
     public final TaskbarAllAppsController taskbarAllAppsController;
     public final TaskbarInsetsController taskbarInsetsController;
+    public final VoiceInteractionWindowController voiceInteractionWindowController;
 
     @Nullable private LoggableTaskbarController[] mControllersToLog = null;
 
@@ -80,7 +81,8 @@
             TaskbarPopupController taskbarPopupController,
             TaskbarForceVisibleImmersiveController taskbarForceVisibleImmersiveController,
             TaskbarAllAppsController taskbarAllAppsController,
-            TaskbarInsetsController taskbarInsetsController) {
+            TaskbarInsetsController taskbarInsetsController,
+            VoiceInteractionWindowController voiceInteractionWindowController) {
         this.taskbarActivityContext = taskbarActivityContext;
         this.taskbarDragController = taskbarDragController;
         this.navButtonController = navButtonController;
@@ -99,6 +101,7 @@
         this.taskbarForceVisibleImmersiveController = taskbarForceVisibleImmersiveController;
         this.taskbarAllAppsController = taskbarAllAppsController;
         this.taskbarInsetsController = taskbarInsetsController;
+        this.voiceInteractionWindowController = voiceInteractionWindowController;
     }
 
     /**
@@ -126,13 +129,15 @@
         taskbarAllAppsController.init(this, sharedState.allAppsVisible);
         navButtonController.init(this);
         taskbarInsetsController.init(this);
+        voiceInteractionWindowController.init(this);
 
         mControllersToLog = new LoggableTaskbarController[] {
                 taskbarDragController, navButtonController, navbarButtonsViewController,
                 taskbarDragLayerController, taskbarScrimViewController, taskbarViewController,
                 taskbarUnfoldAnimationController, taskbarKeyguardController,
                 stashedHandleViewController, taskbarStashController, taskbarEduController,
-                taskbarAutohideSuspendController, taskbarPopupController, taskbarInsetsController
+                taskbarAutohideSuspendController, taskbarPopupController, taskbarInsetsController,
+                voiceInteractionWindowController
         };
 
         mAreAllControllersInitialized = true;
@@ -172,6 +177,7 @@
         taskbarAllAppsController.onDestroy();
         navButtonController.onDestroy();
         taskbarInsetsController.onDestroy();
+        voiceInteractionWindowController.onDestroy();
 
         mControllersToLog = null;
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index c522888..04fcc44 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.taskbar;
 
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APPS;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
 
 import android.animation.Animator;
@@ -435,7 +436,7 @@
         if (tag instanceof ItemInfo) {
             ItemInfo item = (ItemInfo) tag;
             TaskbarViewController taskbarViewController = mControllers.taskbarViewController;
-            if (item.container == CONTAINER_ALL_APPS) {
+            if (item.container == CONTAINER_ALL_APPS || item.container == CONTAINER_PREDICTION) {
                 // Since all apps closes when the drag starts, target the all apps button instead.
                 target = taskbarViewController.getAllAppsButtonView();
             } else if (item.container >= 0) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
index 6a6a693..e4f82d2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
@@ -18,6 +18,7 @@
 import android.graphics.Insets
 import android.graphics.Region
 import android.view.InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES
+import android.view.InsetsState
 import android.view.WindowManager
 import com.android.launcher3.AbstractFloatingView
 import com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_ALL_APPS
@@ -61,9 +62,6 @@
             )
         )
 
-        windowLayoutParams.providedInternalInsets = arrayOfNulls<Insets>(ITYPE_SIZE)
-        windowLayoutParams.providedInternalImeInsets = arrayOfNulls<Insets>(ITYPE_SIZE)
-
         onTaskbarWindowHeightOrInsetsChanged()
 
         windowLayoutParams.insetsRoundedCornerFrame = true
@@ -75,32 +73,23 @@
     }
 
     fun onTaskbarWindowHeightOrInsetsChanged() {
-        var reducingSize = getReducingInsetsForTaskbarInsetsHeight(
-            controllers.taskbarStashController.contentHeightToReportToApps)
+        var contentHeight = controllers.taskbarStashController.contentHeightToReportToApps
+        contentRegion.set(0, windowLayoutParams.height - contentHeight,
+            context.deviceProfile.widthPx, windowLayoutParams.height)
+        var tappableHeight = controllers.taskbarStashController.tappableHeightToReportToApps
+        for (provider in windowLayoutParams.providedInsets) {
+            if (provider.type == ITYPE_EXTRA_NAVIGATION_BAR) {
+                provider.insetsSize = Insets.of(0, 0, 0, contentHeight)
+            } else if (provider.type == ITYPE_BOTTOM_TAPPABLE_ELEMENT
+                      || provider.type == ITYPE_BOTTOM_MANDATORY_GESTURES) {
+                provider.insetsSize = Insets.of(0, 0, 0, tappableHeight)
+            }
+        }
 
-        contentRegion.set(0, reducingSize.top,
-                context.deviceProfile.widthPx, windowLayoutParams.height)
-        windowLayoutParams.providedInternalInsets[ITYPE_EXTRA_NAVIGATION_BAR] = reducingSize
-        windowLayoutParams.providedInternalInsets[ITYPE_BOTTOM_MANDATORY_GESTURES] = reducingSize
-        reducingSize = getReducingInsetsForTaskbarInsetsHeight(
-            controllers.taskbarStashController.tappableHeightToReportToApps)
-        windowLayoutParams.providedInternalInsets[ITYPE_BOTTOM_TAPPABLE_ELEMENT] = reducingSize
-        windowLayoutParams.providedInternalInsets[ITYPE_BOTTOM_MANDATORY_GESTURES] = reducingSize
-
-        reducingSize = getReducingInsetsForTaskbarInsetsHeight(taskbarHeightForIme)
-        windowLayoutParams.providedInternalImeInsets[ITYPE_EXTRA_NAVIGATION_BAR] = reducingSize
-        windowLayoutParams.providedInternalImeInsets[ITYPE_BOTTOM_TAPPABLE_ELEMENT] = reducingSize
-        windowLayoutParams.providedInternalImeInsets[ITYPE_BOTTOM_MANDATORY_GESTURES] = reducingSize
-    }
-
-    /**
-     * WindowLayoutParams.providedInternal*Insets expects Insets that subtract from the window frame
-     * height (i.e. WindowLayoutParams#height). So for Taskbar to report bottom insets to apps, it
-     * actually provides insets from the top of its window frame.
-     * @param height The number of pixels from the bottom of the screen that Taskbar insets.
-     */
-    private fun getReducingInsetsForTaskbarInsetsHeight(height: Int): Insets {
-        return Insets.of(0, windowLayoutParams.height - height, 0, 0)
+        var imeInsetsSize = Insets.of(0, 0, 0, taskbarHeightForIme)
+        for (provider in windowLayoutParams.providedInsets) {
+            provider.imeInsetsSize = imeInsetsSize
+        }
     }
 
     /**
@@ -151,13 +140,10 @@
     override fun dumpLogs(prefix: String, pw: PrintWriter) {
         pw.println(prefix + "TaskbarInsetsController:")
         pw.println("$prefix\twindowHeight=${windowLayoutParams.height}")
-        pw.println("$prefix\tprovidedInternalInsets[ITYPE_EXTRA_NAVIGATION_BAR]=" +
-                "${windowLayoutParams.providedInternalInsets[ITYPE_EXTRA_NAVIGATION_BAR]}")
-        pw.println("$prefix\tprovidedInternalInsets[ITYPE_BOTTOM_TAPPABLE_ELEMENT]=" +
-                "${windowLayoutParams.providedInternalInsets[ITYPE_BOTTOM_TAPPABLE_ELEMENT]}")
-        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]}")
+        for (provider in windowLayoutParams.providedInsets) {
+            pw.println("$prefix\tprovidedInsets: (type=" + InsetsState.typeToString(provider.type)
+                    + " insetsSize=" + provider.insetsSize
+                    + " imeInsetsSize=" + provider.imeInsetsSize + ")")
+        }
     }
-}
\ No newline at end of file
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index dc0ef27..ff11f67 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -19,11 +19,13 @@
 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_STASHED_LAUNCHER_STATE;
 import static com.android.launcher3.taskbar.TaskbarStashController.TASKBAR_STASH_DURATION;
 import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_HOME;
+import static com.android.systemui.animation.Interpolators.EMPHASIZED;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
+import android.util.Log;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -31,6 +33,7 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.QuickstepTransitionManager;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.util.MultiValueAlpha;
@@ -44,7 +47,6 @@
 import java.io.PrintWriter;
 import java.util.HashMap;
 import java.util.StringJoiner;
-import java.util.function.Consumer;
 import java.util.function.Supplier;
 
 /**
@@ -53,6 +55,9 @@
  */
  public class TaskbarLauncherStateController {
 
+    private static final String TAG = TaskbarLauncherStateController.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
     public static final int FLAG_RESUMED = 1 << 0;
     public static final int FLAG_RECENTS_ANIMATION_RUNNING = 1 << 1;
     public static final int FLAG_TRANSITION_STATE_RUNNING = 1 << 2;
@@ -99,7 +104,11 @@
                     }
                     updateStateForFlag(FLAG_TRANSITION_STATE_RUNNING, true);
                     if (!mShouldDelayLauncherStateAnim) {
-                        applyState();
+                        if (toState == LauncherState.NORMAL) {
+                            applyState(QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION);
+                        } else {
+                            applyState();
+                        }
                     }
                 }
 
@@ -122,7 +131,12 @@
         MultiValueAlpha taskbarIconAlpha = mControllers.taskbarViewController.getTaskbarIconAlpha();
         mIconAlphaForHome = taskbarIconAlpha.getProperty(ALPHA_INDEX_HOME);
         mIconAlphaForHome.setConsumer(
-                (Consumer<Float>) alpha -> mLauncher.getHotseat().setIconsAlpha(alpha > 0 ? 0 : 1));
+                alpha -> {
+                    mLauncher.getHotseat().setIconsAlpha(alpha > 0 ? 0 : 1);
+                    if (mLauncher.getDeviceProfile().isQsbInline) {
+                        mLauncher.getHotseat().setQsbAlpha(alpha > 0 ? 0 : 1);
+                    }
+                });
 
         mIconAlignmentForResumedState.finishAnimation();
         onIconAlignmentRatioChangedForAppAndHomeTransition();
@@ -169,10 +183,8 @@
 
         mTaskBarRecentsAnimationListener = new TaskBarRecentsAnimationListener(callbacks);
         callbacks.addListener(mTaskBarRecentsAnimationListener);
-        RecentsView recentsView = mLauncher.getOverviewPanel();
-        recentsView.setTaskLaunchListener(() -> {
-            mTaskBarRecentsAnimationListener.endGestureStateOverride(true);
-        });
+        ((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchListener(() ->
+                mTaskBarRecentsAnimationListener.endGestureStateOverride(true));
         return animatorSet;
     }
 
@@ -269,6 +281,11 @@
                 ObjectAnimator resumeAlignAnim = mIconAlignmentForResumedState
                         .animateToValue(toAlignmentForResumedState)
                         .setDuration(duration);
+                if (DEBUG) {
+                    Log.d(TAG, "mIconAlignmentForResumedState - "
+                            + mIconAlignmentForResumedState.value
+                            + " -> " + toAlignmentForResumedState + ": " + duration);
+                }
 
                 resumeAlignAnim.addListener(new AnimatorListenerAdapter() {
                     @Override
@@ -305,6 +322,11 @@
                 if (isRecentsAnimationRunning) {
                     gestureAlignAnim.setDuration(duration);
                 }
+                if (DEBUG) {
+                    Log.d(TAG, "mIconAlignmentForGestureState - "
+                            + mIconAlignmentForGestureState.value
+                            + " -> " + toAlignmentForGestureState + ": " + duration);
+                }
                 gestureAlignAnim.addListener(new AnimatorListenerAdapter() {
                     @Override
                     public void onAnimationEnd(Animator animation) {
@@ -330,6 +352,7 @@
                     .setDuration(duration));
         }
 
+        animatorSet.setInterpolator(EMPHASIZED);
         if (start) {
             animatorSet.start();
         }
@@ -374,6 +397,12 @@
             mIconAlignmentForLauncherState.finishAnimation();
             animatorSet.play(mIconAlignmentForLauncherState.animateToValue(toAlignment)
                     .setDuration(duration));
+            if (DEBUG) {
+                Log.d(TAG, "mIconAlignmentForLauncherState - "
+                        + mIconAlignmentForLauncherState.value
+                        + " -> " + toAlignment + ": " + duration);
+            }
+            animatorSet.setInterpolator(EMPHASIZED);
         }
     }
 
@@ -396,17 +425,17 @@
         onIconAlignmentRatioChanged(this::getCurrentIconAlignmentRatioBetweenAppAndHome);
     }
 
-    private void onIconAlignmentRatioChanged(Supplier<Float> alignmentSupplier) {
+    private void onIconAlignmentRatioChanged(Supplier<AnimatedFloat> alignmentSupplier) {
         if (mControllers == null) {
             return;
         }
-        float alignment = alignmentSupplier.get();
+        AnimatedFloat animatedFloat = alignmentSupplier.get();
         float currentValue = mIconAlphaForHome.getValue();
-        boolean taskbarWillBeVisible = alignment < 1;
+        boolean taskbarWillBeVisible = animatedFloat.value < 1;
         boolean firstFrameVisChanged = (taskbarWillBeVisible && Float.compare(currentValue, 1) != 0)
                 || (!taskbarWillBeVisible && Float.compare(currentValue, 0) != 0);
 
-        updateIconAlignment(alignment);
+        updateIconAlignment(animatedFloat.value, animatedFloat.getEndValue());
 
         // Sync the first frame where we swap taskbar and hotseat.
         if (firstFrameVisChanged && mCanSyncViews && !Utilities.IS_RUNNING_IN_TEST_HARNESS) {
@@ -416,21 +445,22 @@
         }
     }
 
-    private void updateIconAlignment(float alignment) {
+    private void updateIconAlignment(float alignment, Float endAlignment) {
         mControllers.taskbarViewController.setLauncherIconAlignment(
-                alignment, mLauncher.getDeviceProfile());
+                alignment, endAlignment, mLauncher.getDeviceProfile());
 
         // Switch taskbar and hotseat in last frame
         setTaskbarViewVisible(alignment < 1);
         mControllers.navbarButtonsViewController.updateTaskbarAlignment(alignment);
     }
 
-    private float getCurrentIconAlignmentRatioBetweenAppAndHome() {
-        return Math.max(mIconAlignmentForResumedState.value, mIconAlignmentForGestureState.value);
+    private AnimatedFloat getCurrentIconAlignmentRatioBetweenAppAndHome() {
+        return mIconAlignmentForResumedState.value > mIconAlignmentForGestureState.value
+                ? mIconAlignmentForResumedState : mIconAlignmentForGestureState;
     }
 
-    private float getCurrentIconAlignmentRatioForLauncherState() {
-        return mIconAlignmentForLauncherState.value;
+    private AnimatedFloat getCurrentIconAlignmentRatioForLauncherState() {
+        return mIconAlignmentForLauncherState;
     }
 
     private void setTaskbarViewVisible(boolean isVisible) {
@@ -459,6 +489,7 @@
         private void endGestureStateOverride(boolean finishedToApp) {
             mCallbacks.removeListener(this);
             mTaskBarRecentsAnimationListener = null;
+            ((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchListener(null);
 
             // Update the resumed state immediately to ensure a seamless handoff
             boolean launcherResumed = !finishedToApp;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
index 4ff0649..3392b6b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
@@ -33,6 +33,8 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.util.Log;
+import android.view.HapticFeedbackConstants;
+import android.view.View;
 
 import androidx.annotation.IntDef;
 import androidx.annotation.Nullable;
@@ -113,7 +115,9 @@
         mHandler = handler;
     }
 
-    public void onButtonClick(@TaskbarButton int buttonType) {
+    public void onButtonClick(@TaskbarButton int buttonType, View view) {
+        // Provide the same haptic feedback that the system offers for virtual keys.
+        view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
         switch (buttonType) {
             case BUTTON_BACK:
                 logEvent(LAUNCHER_TASKBAR_BACK_BUTTON_TAP);
@@ -144,7 +148,9 @@
         }
     }
 
-    public boolean onButtonLongClick(@TaskbarButton int buttonType) {
+    public boolean onButtonLongClick(@TaskbarButton int buttonType, View view) {
+        // Provide the same haptic feedback that the system offers for virtual keys.
+        view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
         switch (buttonType) {
             case BUTTON_HOME:
                 logEvent(LAUNCHER_TASKBAR_HOME_BUTTON_LONGPRESS);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index fc9f9d0..2b8fdd1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -22,6 +22,7 @@
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_SHOW;
 import static com.android.launcher3.taskbar.Utilities.appendFlag;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
 
 import android.animation.Animator;
@@ -30,13 +31,15 @@
 import android.annotation.Nullable;
 import android.content.SharedPreferences;
 import android.util.Log;
+import android.view.View;
 import android.view.ViewConfiguration;
-import android.view.WindowInsets;
 
+import androidx.annotation.NonNull;
+
+import com.android.internal.jank.InteractionJankMonitor;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorListeners;
-import com.android.launcher3.taskbar.allapps.TaskbarAllAppsSlideInView;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
 import com.android.quickstep.AnimatedFloat;
@@ -44,6 +47,8 @@
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
 import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Optional;
 import java.util.StringJoiner;
 import java.util.function.IntPredicate;
 
@@ -53,6 +58,8 @@
  */
 public class TaskbarStashController implements TaskbarControllers.LoggableTaskbarController {
 
+    private static final String TAG = "TaskbarStashController";
+
     public static final int FLAG_IN_APP = 1 << 0;
     public static final int FLAG_STASHED_IN_APP_MANUAL = 1 << 1; // long press, persisted
     public static final int FLAG_STASHED_IN_APP_PINNED = 1 << 2; // app pinning
@@ -149,6 +156,7 @@
     private @Nullable AnimatorSet mAnimator;
     private boolean mIsSystemGestureInProgress;
     private boolean mIsImeShowing;
+    private boolean mIsImeSwitcherShowing;
 
     private boolean mEnableManualStashingForTests = false;
 
@@ -401,6 +409,7 @@
             mAnimator.cancel();
         }
         mAnimator = new AnimatorSet();
+        addJankMonitorListener(mAnimator, /* appearing= */ !mIsStashed);
 
         if (!supportsVisualStashing()) {
             // Just hide/show the icons and background instead of stashing into a handle.
@@ -496,6 +505,28 @@
         });
     }
 
+    private void addJankMonitorListener(AnimatorSet animator, boolean expanding) {
+        Optional<View> optionalView =
+                Arrays.stream(mControllers.taskbarViewController.getIconViews()).findFirst();
+        if (optionalView.isEmpty()) {
+            Log.wtf(TAG, "No views to start Interaction jank monitor with.", new Exception());
+            return;
+        }
+        View v = optionalView.get();
+        int action = expanding ? InteractionJankMonitor.CUJ_TASKBAR_EXPAND :
+                InteractionJankMonitor.CUJ_TASKBAR_COLLAPSE;
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(@NonNull Animator animation) {
+                InteractionJankMonitor.getInstance().begin(v, action);
+            }
+
+            @Override
+            public void onAnimationEnd(@NonNull Animator animation) {
+                InteractionJankMonitor.getInstance().end(action);
+            }
+        });
+    }
     /**
      * Creates and starts a partial stash animation, hinting at the new state that will trigger when
      * long press is detected.
@@ -571,9 +602,11 @@
         }
 
         // Only update the following flags when system gesture is not in progress.
-        maybeResetStashedInAppAllApps(hasAnyFlag(FLAG_STASHED_IN_APP_IME) == mIsImeShowing);
-        if (hasAnyFlag(FLAG_STASHED_IN_APP_IME) != mIsImeShowing) {
-            updateStateForFlag(FLAG_STASHED_IN_APP_IME, mIsImeShowing);
+        boolean shouldStashForIme = shouldStashForIme();
+        maybeResetStashedInAppAllApps(
+                hasAnyFlag(FLAG_STASHED_IN_APP_IME) == shouldStashForIme);
+        if (hasAnyFlag(FLAG_STASHED_IN_APP_IME) != shouldStashForIme) {
+            updateStateForFlag(FLAG_STASHED_IN_APP_IME, shouldStashForIme);
             applyState(TASKBAR_STASH_DURATION_FOR_IME, getTaskbarStashStartDelayForIme());
         }
     }
@@ -625,8 +658,9 @@
 
         // Only update FLAG_STASHED_IN_APP_IME when system gesture is not in progress.
         mIsImeShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SHOWING);
+        mIsImeSwitcherShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SWITCHER_SHOWING);
         if (!mIsSystemGestureInProgress) {
-            updateStateForFlag(FLAG_STASHED_IN_APP_IME, mIsImeShowing);
+            updateStateForFlag(FLAG_STASHED_IN_APP_IME, shouldStashForIme());
             animDuration = TASKBAR_STASH_DURATION_FOR_IME;
             startDelay = getTaskbarStashStartDelayForIme();
         }
@@ -634,6 +668,10 @@
         applyState(skipAnim ? 0 : animDuration, skipAnim ? 0 : startDelay);
     }
 
+    private boolean shouldStashForIme() {
+        return mIsImeShowing || mIsImeSwitcherShowing;
+    }
+
     /**
      * Updates the proper flag to indicate whether the task bar should be stashed.
      *
@@ -699,6 +737,7 @@
         pw.println(String.format(
                 "%s\tmIsSystemGestureInProgress=%b", prefix, mIsSystemGestureInProgress));
         pw.println(String.format("%s\tmIsImeShowing=%b", prefix, mIsImeShowing));
+        pw.println(String.format("%s\tmIsImeSwitcherShowing=%b", prefix, mIsImeSwitcherShowing));
     }
 
     private static String getStateString(int flags) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 6f88d64..6a43cc5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -20,6 +20,7 @@
 import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.util.AttributeSet;
+import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
@@ -31,6 +32,7 @@
 import androidx.core.graphics.ColorUtils;
 
 import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
@@ -78,6 +80,8 @@
     // Only non-null when device supports having an All Apps button.
     private @Nullable AllAppsButton mAllAppsButton;
 
+    private View mQsb;
+
     public TaskbarView(@NonNull Context context) {
         this(context, null);
     }
@@ -117,6 +121,9 @@
                     new ViewGroup.LayoutParams(mIconTouchSize, mIconTouchSize));
             mAllAppsButton.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding);
         }
+
+        // TODO: Disable touch events on QSB otherwise it can crash.
+        mQsb = LayoutInflater.from(context).inflate(R.layout.search_container_hotseat, this, false);
     }
 
     private int getColorWithGivenLuminance(int color, float luminance) {
@@ -166,6 +173,7 @@
         if (mAllAppsButton != null) {
             removeView(mAllAppsButton);
         }
+        removeView(mQsb);
 
         for (int i = 0; i < hotseatItemInfos.length; i++) {
             ItemInfo hotseatItemInfo = hotseatItemInfos[i];
@@ -242,6 +250,11 @@
             int index = Utilities.isRtl(getResources()) ? 0 : getChildCount();
             addView(mAllAppsButton, index);
         }
+        if (mActivityContext.getDeviceProfile().isQsbInline) {
+            addView(mQsb, Utilities.isRtl(getResources()) ? getChildCount() : 0);
+            // Always set QSB to invisible after re-adding.
+            mQsb.setVisibility(View.INVISIBLE);
+        }
 
         mThemeIconsBackground = calculateThemeIconsBackground();
         setThemedIconsBackgroundColor(mThemeIconsBackground);
@@ -273,7 +286,12 @@
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         int count = getChildCount();
-        int spaceNeeded = count * (mItemMarginLeftRight * 2 + mIconTouchSize);
+        int countExcludingQsb = count;
+        DeviceProfile deviceProfile = mActivityContext.getDeviceProfile();
+        if (deviceProfile.isQsbInline) {
+            countExcludingQsb--;
+        }
+        int spaceNeeded = countExcludingQsb * (mItemMarginLeftRight * 2 + mIconTouchSize);
         int navSpaceNeeded = ApiWrapper.getHotseatEndOffset(getContext());
         boolean layoutRtl = isLayoutRtl();
         int iconEnd = right - (right - left - spaceNeeded) / 2;
@@ -292,10 +310,25 @@
         mIconLayoutBounds.bottom = mIconLayoutBounds.top + mIconTouchSize;
         for (int i = count; i > 0; i--) {
             View child = getChildAt(i - 1);
-            iconEnd -= mItemMarginLeftRight;
-            int iconStart = iconEnd - mIconTouchSize;
-            child.layout(iconStart, mIconLayoutBounds.top, iconEnd, mIconLayoutBounds.bottom);
-            iconEnd = iconStart - mItemMarginLeftRight;
+            if (child == mQsb) {
+                int qsbStart;
+                int qsbEnd;
+                if (layoutRtl) {
+                    qsbStart = iconEnd + mItemMarginLeftRight;
+                    qsbEnd = qsbStart + deviceProfile.qsbWidth;
+                } else {
+                    qsbEnd = iconEnd - mItemMarginLeftRight;
+                    qsbStart = qsbEnd - deviceProfile.qsbWidth;
+                }
+                int qsbTop = (bottom - top - deviceProfile.hotseatQsbHeight) / 2;
+                int qsbBottom = qsbTop + deviceProfile.hotseatQsbHeight;
+                child.layout(qsbStart, qsbTop, qsbEnd, qsbBottom);
+            } else {
+                iconEnd -= mItemMarginLeftRight;
+                int iconStart = iconEnd - mIconTouchSize;
+                child.layout(iconStart, mIconLayoutBounds.top, iconEnd, mIconLayoutBounds.bottom);
+                iconEnd = iconStart - mItemMarginLeftRight;
+            }
         }
         mIconLayoutBounds.left = iconEnd;
     }
@@ -367,6 +400,13 @@
         return mAllAppsButton;
     }
 
+    /**
+     * Returns the QSB in the taskbar.
+     */
+    public View getQsb() {
+        return mQsb;
+    }
+
     // FolderIconParent implemented methods.
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 3562f5b..db7dc78 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.taskbar;
 
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
 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;
@@ -35,12 +36,15 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AlphaUpdateListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.icons.ThemedIconDrawable;
 import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.util.HorizontalInsettableView;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.LauncherBindableItemsContainer;
 import com.android.launcher3.util.MultiValueAlpha;
@@ -63,7 +67,9 @@
     public static final int ALPHA_INDEX_STASH = 2;
     public static final int ALPHA_INDEX_RECENTS_DISABLED = 3;
     public static final int ALPHA_INDEX_NOTIFICATION_EXPANDED = 4;
-    private static final int NUM_ALPHA_CHANNELS = 5;
+    public static final int ALPHA_INDEX_ASSISTANT_INVOKED = 5;
+    public static final int ALPHA_INDEX_IME_BUTTON_NAV = 6;
+    private static final int NUM_ALPHA_CHANNELS = 7;
 
     private final TaskbarActivityContext mActivity;
     private final TaskbarView mTaskbarView;
@@ -138,6 +144,14 @@
     }
 
     /**
+     * Should be called when the IME switcher visibility changes.
+     */
+    public void setIsImeSwitcherVisible(boolean isImeSwitcherVisible) {
+        mTaskbarIconAlpha.getProperty(ALPHA_INDEX_IME_BUTTON_NAV).setValue(
+                isImeSwitcherVisible ? 0 : 1);
+    }
+
+    /**
      * Should be called when the recents button is disabled, so we can hide taskbar icons as well.
      */
     public void setRecentsButtonDisabled(boolean isDisabled) {
@@ -210,9 +224,10 @@
      *                       0 => not aligned
      *                       1 => fully aligned
      */
-    public void setLauncherIconAlignment(float alignmentRatio, DeviceProfile launcherDp) {
+    public void setLauncherIconAlignment(float alignmentRatio, Float endAlignment,
+            DeviceProfile launcherDp) {
         if (mIconAlignControllerLazy == null) {
-            mIconAlignControllerLazy = createIconAlignmentController(launcherDp);
+            mIconAlignControllerLazy = createIconAlignmentController(launcherDp, endAlignment);
         }
         mIconAlignControllerLazy.setPlayFraction(alignmentRatio);
         if (alignmentRatio <= 0 || alignmentRatio >= 1) {
@@ -224,11 +239,13 @@
     /**
      * Creates an animation for aligning the taskbar icons with the provided Launcher device profile
      */
-    private AnimatorPlaybackController createIconAlignmentController(DeviceProfile launcherDp) {
+    private AnimatorPlaybackController createIconAlignmentController(DeviceProfile launcherDp,
+            Float endAlignment) {
         mOnControllerPreCreateCallback.run();
         PendingAnimation setter = new PendingAnimation(100);
+        DeviceProfile taskbarDp = mActivity.getDeviceProfile();
         Rect hotseatPadding = launcherDp.getHotseatLayoutPadding(mActivity);
-        float scaleUp = ((float) launcherDp.iconSizePx) / mActivity.getDeviceProfile().iconSizePx;
+        float scaleUp = ((float) launcherDp.iconSizePx) / taskbarDp.iconSizePx;
         int borderSpacing = launcherDp.hotseatBorderSpace;
         int hotseatCellSize = DeviceProfile.calculateCellWidth(
                 launcherDp.availableWidthPx - hotseatPadding.left - hotseatPadding.right,
@@ -245,14 +262,13 @@
         }
 
         int collapsedHeight = mActivity.getDefaultTaskbarWindowHeight();
-        int expandedHeight = Math.max(collapsedHeight,
-                mActivity.getDeviceProfile().taskbarSize + offsetY);
+        int expandedHeight = Math.max(collapsedHeight, taskbarDp.taskbarSize + offsetY);
         setter.addOnFrameListener(anim -> mActivity.setTaskbarWindowHeight(
                 anim.getAnimatedFraction() > 0 ? expandedHeight : collapsedHeight));
 
+        boolean isToHome = endAlignment != null && endAlignment == 1;
         for (int i = 0; i < mTaskbarView.getChildCount(); i++) {
             View child = mTaskbarView.getChildAt(i);
-
             int positionInHotseat;
             if (FeatureFlags.ENABLE_ALL_APPS_IN_TASKBAR.get()
                     && child == mTaskbarView.getAllAppsButtonView()) {
@@ -260,13 +276,44 @@
                 // as its convenient for animation purposes.
                 positionInHotseat = Utilities.isRtl(child.getResources())
                         ? -1
-                        : mActivity.getDeviceProfile().numShownHotseatIcons;
+                        : taskbarDp.numShownHotseatIcons;
 
                 if (!FeatureFlags.ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT.get()) {
-                    setter.setViewAlpha(child, 0, LINEAR);
+                    setter.setViewAlpha(child, 0,
+                            isToHome
+                                    ? Interpolators.clampToProgress(LINEAR, 0f, 0.17f)
+                                    : Interpolators.clampToProgress(LINEAR, 0.72f, 0.84f));
                 }
             } else if (child.getTag() instanceof ItemInfo) {
                 positionInHotseat = ((ItemInfo) child.getTag()).screenId;
+            } else if (child == mTaskbarView.getQsb()) {
+                boolean isRtl = Utilities.isRtl(child.getResources());
+                float hotseatIconCenter = isRtl
+                        ? launcherDp.widthPx - hotseatPadding.right + borderSpacing
+                        + launcherDp.qsbWidth / 2f
+                        : hotseatPadding.left - borderSpacing - launcherDp.qsbWidth / 2f;
+                float childCenter = (child.getLeft() + child.getRight()) / 2f;
+                float halfQsbIconWidthDiff = (launcherDp.qsbWidth - taskbarDp.iconSizePx) / 2f;
+                setter.addFloat(child, ICON_TRANSLATE_X,
+                        isRtl ? -halfQsbIconWidthDiff : halfQsbIconWidthDiff,
+                        hotseatIconCenter - childCenter, LINEAR);
+
+                float scale = ((float) taskbarDp.iconSizePx) / launcherDp.hotseatQsbVisualHeight;
+                setter.addFloat(child, SCALE_PROPERTY, scale, 1f, LINEAR);
+
+                setter.addFloat(child, VIEW_ALPHA, 0f, 1f,
+                        isToHome
+                                ? Interpolators.clampToProgress(LINEAR, 0f, 0.35f)
+                                : Interpolators.clampToProgress(LINEAR, 0.84f, 1f));
+                setter.addOnFrameListener(animator -> AlphaUpdateListener.updateVisibility(child));
+
+                float qsbInsetFraction = halfQsbIconWidthDiff / launcherDp.qsbWidth;
+                if (child instanceof  HorizontalInsettableView) {
+                    setter.addFloat((HorizontalInsettableView) child,
+                            HorizontalInsettableView.HORIZONTAL_INSETS, qsbInsetFraction, 0,
+                            LINEAR);
+                }
+                continue;
             } else {
                 Log.w(TAG, "Unsupported view found in createIconAlignmentController, v=" + child);
                 continue;
diff --git a/quickstep/src/com/android/launcher3/taskbar/VoiceInteractionWindowController.kt b/quickstep/src/com/android/launcher3/taskbar/VoiceInteractionWindowController.kt
new file mode 100644
index 0000000..946873e
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/VoiceInteractionWindowController.kt
@@ -0,0 +1,109 @@
+package com.android.launcher3.taskbar
+
+import android.graphics.Canvas
+import android.view.WindowManager
+import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
+import com.android.launcher3.views.BaseDragLayer
+import com.android.systemui.animation.ViewRootSync
+import java.io.PrintWriter
+
+private const val TASKBAR_ICONS_FADE_DURATION = 300L
+private const val STASHED_HANDLE_FADE_DURATION = 180L
+
+/**
+ * Controls Taskbar behavior while Voice Interaction Window (assistant) is showing.
+ */
+class VoiceInteractionWindowController(val context: TaskbarActivityContext)
+    : TaskbarControllers.LoggableTaskbarController {
+
+    private val taskbarBackgroundRenderer = TaskbarBackgroundRenderer(context)
+
+    // Initialized in init.
+    private lateinit var controllers: TaskbarControllers
+    private lateinit var separateWindowForTaskbarBackground: BaseDragLayer<TaskbarActivityContext>
+    private lateinit var separateWindowLayoutParams: WindowManager.LayoutParams
+
+    private var isVoiceInteractionWindowVisible: Boolean = false
+
+    fun init(controllers: TaskbarControllers) {
+        this.controllers = controllers
+
+        separateWindowForTaskbarBackground =
+            object : BaseDragLayer<TaskbarActivityContext>(context, null, 0) {
+                override fun recreateControllers() {
+                    mControllers = emptyArray()
+                }
+
+                override fun draw(canvas: Canvas) {
+                    super.draw(canvas)
+                    taskbarBackgroundRenderer.draw(canvas)
+                }
+            }
+        separateWindowForTaskbarBackground.recreateControllers()
+        separateWindowForTaskbarBackground.setWillNotDraw(false)
+
+        separateWindowLayoutParams = context.createDefaultWindowLayoutParams(
+            TYPE_APPLICATION_OVERLAY)
+        separateWindowLayoutParams.isSystemApplicationOverlay = true
+    }
+
+    fun onDestroy() {
+        setIsVoiceInteractionWindowVisible(visible = false, skipAnim = true)
+    }
+
+    fun setIsVoiceInteractionWindowVisible(visible: Boolean, skipAnim: Boolean) {
+        if (isVoiceInteractionWindowVisible == visible) {
+            return
+        }
+        isVoiceInteractionWindowVisible = visible
+
+        // Fade out taskbar icons and stashed handle.
+        val taskbarIconAlpha = if (isVoiceInteractionWindowVisible) 0f else 1f
+        val fadeTaskbarIcons = controllers.taskbarViewController.taskbarIconAlpha
+            .getProperty(TaskbarViewController.ALPHA_INDEX_ASSISTANT_INVOKED)
+            .animateToValue(taskbarIconAlpha)
+            .setDuration(TASKBAR_ICONS_FADE_DURATION)
+        val fadeStashedHandle = controllers.stashedHandleViewController.stashedHandleAlpha
+            .getProperty(StashedHandleViewController.ALPHA_INDEX_ASSISTANT_INVOKED)
+            .animateToValue(taskbarIconAlpha)
+            .setDuration(STASHED_HANDLE_FADE_DURATION)
+        fadeTaskbarIcons.start()
+        fadeStashedHandle.start()
+        if (skipAnim) {
+            fadeTaskbarIcons.end()
+            fadeStashedHandle.end()
+        }
+
+        if (context.isGestureNav && controllers.taskbarStashController.isInAppAndNotStashed) {
+            moveTaskbarBackgroundToLowerLayer()
+        }
+    }
+
+    /**
+     * Hides the TaskbarDragLayer background and creates a new window to draw just that background.
+     */
+    private fun moveTaskbarBackgroundToLowerLayer() {
+        val taskbarBackgroundOverride = controllers.taskbarDragLayerController
+            .overrideBackgroundAlpha
+        if (isVoiceInteractionWindowVisible) {
+            // First add the temporary window, then hide the overlapping taskbar background.
+            context.addWindowView(separateWindowForTaskbarBackground, separateWindowLayoutParams)
+            ViewRootSync.synchronizeNextDraw(separateWindowForTaskbarBackground, context.dragLayer
+            ) {
+                taskbarBackgroundOverride.updateValue(0f)
+            }
+        } else {
+            // First reapply the original taskbar background, then remove the temporary window.
+            taskbarBackgroundOverride.updateValue(1f)
+            ViewRootSync.synchronizeNextDraw(separateWindowForTaskbarBackground, context.dragLayer
+            ) {
+                context.removeWindowView(separateWindowForTaskbarBackground)
+            }
+        }
+    }
+
+    override fun dumpLogs(prefix: String, pw: PrintWriter) {
+        pw.println(prefix + "VoiceInteractionWindowController:")
+        pw.println("$prefix\tisVoiceInteractionWindowVisible=$isVoiceInteractionWindowVisible")
+    }
+}
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
index 6fd98db..6c43e50 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
@@ -171,8 +171,8 @@
      * This method should be called after an exit animation finishes, if applicable.
      */
     void maybeCloseWindow() {
-        if (AbstractFloatingView.getOpenView(mAllAppsContext, TYPE_ALL) != null
-                || mAllAppsContext.getDragController().isSystemDragInProgress()) {
+        if (mAllAppsContext != null && (AbstractFloatingView.hasOpenView(mAllAppsContext, TYPE_ALL)
+                || mAllAppsContext.getDragController().isSystemDragInProgress())) {
             return;
         }
         mProxyView.close(false);
@@ -187,7 +187,7 @@
         TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
         Optional.ofNullable(mAllAppsContext)
                 .map(c -> c.getSystemService(WindowManager.class))
-                .ifPresent(m -> m.removeView(mAllAppsContext.getDragLayer()));
+                .ifPresent(m -> m.removeViewImmediate(mAllAppsContext.getDragLayer()));
         mAllAppsContext = null;
     }
 
@@ -207,7 +207,7 @@
     private LayoutParams createLayoutParams() {
         LayoutParams layoutParams = new LayoutParams(
                 TYPE_APPLICATION_OVERLAY,
-                0,
+                WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
                 PixelFormat.TRANSLUCENT);
         layoutParams.setTitle(WINDOW_TITLE);
         layoutParams.gravity = Gravity.BOTTOM;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
index 2d7fe69..4d2f965 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -42,17 +42,10 @@
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_SCRIM_FADE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_SCALE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
-import static com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController.ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD;
-import static com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController.ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD;
-import static com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController.ALL_APPS_SCRIM_OPAQUE_THRESHOLD;
-import static com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController.ALL_APPS_SCRIM_VISIBLE_THRESHOLD;
 import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
-import static com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE;
-import static com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE;
 
 import android.animation.ValueAnimator;
 
@@ -60,8 +53,8 @@
 import com.android.launcher3.Hotseat;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.Workspace;
-import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.touch.AllAppsSwipeController;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.util.DisplayController;
 import com.android.quickstep.util.RecentsAtomicAnimationFactory;
@@ -182,23 +175,9 @@
             }
             config.duration = Math.max(config.duration, mHintToNormalDuration);
         } else if (fromState == ALL_APPS && toState == NORMAL) {
-            boolean isTablet = mActivity.getDeviceProfile().isTablet;
-            config.setInterpolator(ANIM_ALL_APPS_FADE,
-                    isTablet ? FINAL_FRAME : Interpolators.clampToProgress(LINEAR,
-                            1 - ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD,
-                            1 - ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD));
-            config.setInterpolator(ANIM_SCRIM_FADE, Interpolators.clampToProgress(LINEAR,
-                    1 - ALL_APPS_SCRIM_OPAQUE_THRESHOLD,
-                    1 - ALL_APPS_SCRIM_VISIBLE_THRESHOLD));
-            config.setInterpolator(ANIM_VERTICAL_PROGRESS, EMPHASIZED_ACCELERATE);
-            if (!isTablet) {
-                config.setInterpolator(ANIM_WORKSPACE_FADE, INSTANT);
-            }
+            AllAppsSwipeController.applyAllAppsToNormalConfig(mActivity, config);
         } else if (fromState == NORMAL && toState == ALL_APPS) {
-            if (mActivity.getDeviceProfile().isTablet) {
-                config.setInterpolator(ANIM_VERTICAL_PROGRESS, EMPHASIZED_DECELERATE);
-            }
-            // TODO(b/231682175): centralize this setup in AllAppsSwipeController
+            AllAppsSwipeController.applyNormalToAllAppsAnimConfig(mActivity, config);
         }
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index e56c90c..9efbc34 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -21,21 +21,8 @@
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
-import static com.android.launcher3.anim.Interpolators.INSTANT;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_DEPTH;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_FADE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_SCALE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_TRANSLATE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_SCRIM_FADE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_SCALE;
 
 import android.view.MotionEvent;
-import android.view.animation.Interpolator;
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
@@ -44,6 +31,7 @@
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.touch.AbstractStateChangeTouchController;
+import com.android.launcher3.touch.AllAppsSwipeController;
 import com.android.launcher3.touch.SingleAxisSwipeDetector;
 import com.android.launcher3.uioverrides.states.OverviewState;
 import com.android.quickstep.SystemUiProxy;
@@ -58,53 +46,6 @@
 
     private static final String TAG = "PortraitStatesTouchCtrl";
 
-    /**
-     * The progress at which all apps content will be fully visible.
-     */
-    public static final float ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD = 0.8f;
-
-    /**
-     * Minimum clamping progress for fading in all apps content
-     */
-    public static final float ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD = 0.5f;
-
-    /**
-     * Minimum clamping progress for fading in all apps scrim
-     */
-    public static final float ALL_APPS_SCRIM_VISIBLE_THRESHOLD = .1f;
-
-    /**
-     * Maximum clamping progress for opaque all apps scrim
-     */
-    public static final float ALL_APPS_SCRIM_OPAQUE_THRESHOLD = .5f;
-
-    // Custom timing for NORMAL -> ALL_APPS on phones only.
-    private static final float ALL_APPS_STATE_TRANSITION = 0.4f;
-    private static final float ALL_APPS_FULL_DEPTH_PROGRESS = 0.5f;
-
-    // Custom interpolators for NORMAL -> ALL_APPS on phones only.
-    private static final Interpolator LINEAR_EARLY =
-            Interpolators.clampToProgress(LINEAR, 0f, ALL_APPS_STATE_TRANSITION);
-    private static final Interpolator STEP_TRANSITION =
-            Interpolators.clampToProgress(FINAL_FRAME, 0f, ALL_APPS_STATE_TRANSITION);
-    // The blur to and from All Apps is set to be complete when the interpolator is at 0.5.
-    public static final Interpolator BLUR =
-            Interpolators.clampToProgress(
-                    Interpolators.mapToProgress(LINEAR, 0f, ALL_APPS_FULL_DEPTH_PROGRESS),
-                    0f, ALL_APPS_STATE_TRANSITION);
-    public static final Interpolator WORKSPACE_FADE = STEP_TRANSITION;
-    public static final Interpolator WORKSPACE_SCALE = LINEAR_EARLY;
-    public static final Interpolator HOTSEAT_FADE = STEP_TRANSITION;
-    public static final Interpolator HOTSEAT_SCALE = LINEAR_EARLY;
-    public static final Interpolator HOTSEAT_TRANSLATE = STEP_TRANSITION;
-    public static final Interpolator SCRIM_FADE = LINEAR_EARLY;
-    public static final Interpolator ALL_APPS_FADE =
-            Interpolators.clampToProgress(LINEAR, ALL_APPS_STATE_TRANSITION, 1f);
-    public static final Interpolator ALL_APPS_VERTICAL_PROGRESS =
-            Interpolators.clampToProgress(
-                    Interpolators.mapToProgress(LINEAR, ALL_APPS_STATE_TRANSITION, 1f),
-                    ALL_APPS_STATE_TRANSITION, 1f);
-
     private final PortraitOverviewStateTouchHelper mOverviewPortraitStateTouchHelper;
 
     public PortraitStatesTouchController(Launcher l) {
@@ -160,66 +101,15 @@
         return fromState;
     }
 
-    private StateAnimationConfig getNormalToAllAppsAnimation() {
-        StateAnimationConfig builder = new StateAnimationConfig();
-        if (mLauncher.getDeviceProfile().isTablet) {
-            builder.setInterpolator(ANIM_ALL_APPS_FADE, INSTANT);
-            builder.setInterpolator(ANIM_SCRIM_FADE,
-                    Interpolators.clampToProgress(LINEAR,
-                            ALL_APPS_SCRIM_VISIBLE_THRESHOLD,
-                            ALL_APPS_SCRIM_OPAQUE_THRESHOLD));
-        } else {
-            // TODO(b/231682175): centralize this setup in AllAppsSwipeController.
-            builder.setInterpolator(ANIM_DEPTH, BLUR);
-            builder.setInterpolator(ANIM_WORKSPACE_FADE, WORKSPACE_FADE);
-            builder.setInterpolator(ANIM_WORKSPACE_SCALE, WORKSPACE_SCALE);
-            builder.setInterpolator(ANIM_HOTSEAT_FADE, HOTSEAT_FADE);
-            builder.setInterpolator(ANIM_HOTSEAT_SCALE, HOTSEAT_SCALE);
-            builder.setInterpolator(ANIM_HOTSEAT_TRANSLATE, HOTSEAT_TRANSLATE);
-            builder.setInterpolator(ANIM_SCRIM_FADE, SCRIM_FADE);
-            builder.setInterpolator(ANIM_ALL_APPS_FADE, ALL_APPS_FADE);
-            builder.setInterpolator(ANIM_VERTICAL_PROGRESS, ALL_APPS_VERTICAL_PROGRESS);
-        }
-        return builder;
-    }
-
-    private StateAnimationConfig getAllAppsToNormalAnimation() {
-        StateAnimationConfig builder = new StateAnimationConfig();
-        if (mLauncher.getDeviceProfile().isTablet) {
-            builder.setInterpolator(ANIM_ALL_APPS_FADE, FINAL_FRAME);
-            builder.setInterpolator(ANIM_SCRIM_FADE,
-                    Interpolators.clampToProgress(LINEAR,
-                            1 - ALL_APPS_SCRIM_OPAQUE_THRESHOLD,
-                            1 - ALL_APPS_SCRIM_VISIBLE_THRESHOLD));
-        } else {
-            // These interpolators are the reverse of the ones used above, so swiping out of All
-            // Apps feels the same as swiping into it.
-            // TODO(b/231682175): centralize this setup in AllAppsSwipeController.
-            builder.setInterpolator(ANIM_DEPTH, Interpolators.reverse(BLUR));
-            builder.setInterpolator(ANIM_WORKSPACE_FADE, Interpolators.reverse(WORKSPACE_FADE));
-            builder.setInterpolator(ANIM_WORKSPACE_SCALE, Interpolators.reverse(WORKSPACE_SCALE));
-            builder.setInterpolator(ANIM_HOTSEAT_FADE, Interpolators.reverse(HOTSEAT_FADE));
-            builder.setInterpolator(ANIM_HOTSEAT_SCALE, Interpolators.reverse(HOTSEAT_SCALE));
-            builder.setInterpolator(ANIM_HOTSEAT_TRANSLATE,
-                    Interpolators.reverse(HOTSEAT_TRANSLATE));
-            builder.setInterpolator(ANIM_SCRIM_FADE, Interpolators.reverse(SCRIM_FADE));
-            builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.reverse(ALL_APPS_FADE));
-            builder.setInterpolator(ANIM_VERTICAL_PROGRESS,
-                    Interpolators.reverse(ALL_APPS_VERTICAL_PROGRESS));
-        }
-        return builder;
-    }
-
     @Override
     protected StateAnimationConfig getConfigForStates(
             LauncherState fromState, LauncherState toState) {
-        final StateAnimationConfig config;
+        final StateAnimationConfig config = new StateAnimationConfig();
+        config.userControlled = true;
         if (fromState == NORMAL && toState == ALL_APPS) {
-            config = getNormalToAllAppsAnimation();
+            AllAppsSwipeController.applyNormalToAllAppsAnimConfig(mLauncher, config);
         } else if (fromState == ALL_APPS && toState == NORMAL) {
-            config = getAllAppsToNormalAnimation();
-        } else {
-            config = new StateAnimationConfig();
+            AllAppsSwipeController.applyAllAppsToNormalConfig(mLauncher, config);
         }
         return config;
     }
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 76f7718..9dbfa83 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -230,7 +230,6 @@
                     STATE_LAUNCHER_BIND_TO_SERVICE;
 
     public static final long MAX_SWIPE_DURATION = 350;
-    public static final long HOME_DURATION = StaggeredWorkspaceAnim.DURATION_MS;
 
     public static final float MIN_PROGRESS_FOR_OVERVIEW = 0.7f;
     private static final float SWIPE_DURATION_MULTIPLIER =
@@ -298,9 +297,12 @@
         mActivityInterface = gestureState.getActivityInterface();
         mActivityInitListener = mActivityInterface.createActivityInitListener(this::onActivityInit);
         mInputConsumerProxy =
-                new InputConsumerProxy(context,
-                        () -> mRecentsView.getPagedViewOrientedState().getRecentsActivityRotation(),
-                        inputConsumer, () -> {
+                new InputConsumerProxy(context, /* rotationSupplier = */ () -> {
+                    if (mRecentsView == null) {
+                        return ROTATION_0;
+                    }
+                    return mRecentsView.getPagedViewOrientedState().getRecentsActivityRotation();
+                }, inputConsumer, /* callback = */ () -> {
                     endRunningWindowAnim(mGestureState.getEndTarget() == HOME /* cancel */);
                     endLauncherTransitionController();
                 }, new InputProxyHandlerFactory(mActivityInterface, mGestureState));
@@ -442,7 +444,7 @@
             });
 
         setupRecentsViewUi();
-        linkRecentsViewScroll();
+        mRecentsView.runOnPageScrollsInitialized(this::linkRecentsViewScroll);
         activity.runOnBindToTouchInteractionService(this::onLauncherBindToService);
 
         mActivity.registerActivityLifecycleCallbacks(mLifecycleCallbacks);
@@ -571,7 +573,7 @@
 
     protected void notifyGestureAnimationStartToRecents() {
         Task[] runningTasks;
-        if (mIsSwipeForStagedSplit) {
+        if (mIsSwipeForSplit) {
             int[] splitTaskIds = TopTaskTracker.INSTANCE.get(mContext).getRunningSplitTaskIds();
             runningTasks = mGestureState.getRunningTask().getPlaceholderTasks(splitTaskIds);
         } else {
@@ -862,6 +864,7 @@
         TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
 
         if (mRecentsView != null) {
+            final View rv = mRecentsView;
             mRecentsView.getViewTreeObserver().addOnDrawListener(new OnDrawListener() {
                 boolean mHandled = false;
 
@@ -877,8 +880,7 @@
                     InteractionJankMonitorWrapper.begin(mRecentsView,
                             InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME);
 
-                    mRecentsView.post(() ->
-                            mRecentsView.getViewTreeObserver().removeOnDrawListener(this));
+                    rv.post(() -> rv.getViewTreeObserver().removeOnDrawListener(this));
                 }
             });
         }
@@ -1137,7 +1139,9 @@
             mInputConsumerProxy.enable();
         }
         if (endTarget == HOME) {
-            duration = HOME_DURATION;
+            duration = mActivity != null && mActivity.getDeviceProfile().isTaskbarPresent
+                    ? StaggeredWorkspaceAnim.DURATION_TASKBAR_MS
+                    : StaggeredWorkspaceAnim.DURATION_MS;
             // Early detach the nav bar once the endTarget is determined as HOME
             if (mRecentsAnimationController != null) {
                 mRecentsAnimationController.detachNavigationBarFromApp(true);
@@ -1280,15 +1284,18 @@
                     ? runningTaskTarget.taskInfo.launchCookies
                     : new ArrayList<>();
             boolean isTranslucent = runningTaskTarget != null && runningTaskTarget.isTranslucent;
+            boolean hasValidLeash = runningTaskTarget != null
+                    && runningTaskTarget.leash != null
+                    && runningTaskTarget.leash.isValid();
             boolean appCanEnterPip = !mDeviceState.isPipActive()
-                    && runningTaskTarget != null
+                    && hasValidLeash
                     && runningTaskTarget.allowEnterPip
                     && runningTaskTarget.taskInfo.pictureInPictureParams != null
                     && runningTaskTarget.taskInfo.pictureInPictureParams.isAutoEnterEnabled();
             HomeAnimationFactory homeAnimFactory =
                     createHomeAnimationFactory(cookies, duration, isTranslucent, appCanEnterPip,
                             runningTaskTarget);
-            mIsSwipingPipToHome = !mIsSwipeForStagedSplit && appCanEnterPip;
+            mIsSwipingPipToHome = !mIsSwipeForSplit && appCanEnterPip;
             final RectFSpringAnim[] windowAnim;
             if (mIsSwipingPipToHome) {
                 mSwipePipToHomeAnimator = createWindowAnimationToPip(
@@ -1390,9 +1397,6 @@
         }
     }
 
-    /**
-     * TODO(b/195473090) handle multiple task simulators (if needed) for PIP
-     */
     private SwipePipToHomeAnimator createWindowAnimationToPip(HomeAnimationFactory homeAnimFactory,
             RemoteAnimationTargetCompat runningTaskTarget, float startProgress) {
         // Directly animate the app to PiP (picture-in-picture) mode
@@ -1675,7 +1679,7 @@
         boolean wasVisible = mWasLauncherAlreadyVisible || mGestureStarted;
         mActivityInterface.onTransitionCancelled(wasVisible, mGestureState.getEndTarget());
 
-        if (mRecentsAnimationTargets != null) {
+        if (mRecentsAnimationTargets != null && wasVisible) {
             setDividerShown(true /* shown */, true /* immediate */);
         }
 
@@ -1792,12 +1796,13 @@
                     mSwipePipToHomeAnimator.getFinishTransaction(),
                     mSwipePipToHomeAnimator.getContentOverlay());
             mIsSwipingPipToHome = false;
-        } else if (mIsSwipeForStagedSplit) {
+        } else if (mIsSwipeForSplit) {
             // Transaction to hide the task to avoid flicker for entering PiP from split-screen.
             PictureInPictureSurfaceTransaction tx =
                     new PictureInPictureSurfaceTransaction.Builder()
                             .setAlpha(0f)
                             .build();
+            tx.setShouldDisableCanAffectSystemUiFlags(false);
             int[] taskIds = TopTaskTracker.INSTANCE.get(mContext).getRunningSplitTaskIds();
             for (int taskId : taskIds) {
                 mRecentsAnimationController.setFinishTaskTransaction(taskId,
diff --git a/quickstep/src/com/android/quickstep/AnimatedFloat.java b/quickstep/src/com/android/quickstep/AnimatedFloat.java
index 6c7a885..a166553 100644
--- a/quickstep/src/com/android/quickstep/AnimatedFloat.java
+++ b/quickstep/src/com/android/quickstep/AnimatedFloat.java
@@ -133,4 +133,11 @@
     public boolean isAnimatingToValue(float endValue) {
         return isAnimating() && mEndValue != null && mEndValue == endValue;
     }
+
+    /**
+     * Returns the value we are animating to, or {@code null} if we are not currently animating.
+     */
+    public Float getEndValue() {
+        return mEndValue;
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 2fcd286..52abb92 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -56,11 +56,9 @@
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.NavigationMode;
-import com.android.launcher3.util.WindowBounds;
 import com.android.launcher3.views.ScrimView;
 import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
-import com.android.quickstep.util.SplitScreenBounds;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -258,7 +256,7 @@
 
     private void calculateTaskSizeInternal(Context context, DeviceProfile dp,
             Rect potentialTaskRect, float maxScale, int gravity, Rect outRect) {
-        PointF taskDimension = getTaskDimension(context, dp);
+        PointF taskDimension = getTaskDimension(dp);
 
         float scale = Math.min(
                 potentialTaskRect.width() / taskDimension.x,
@@ -270,47 +268,29 @@
         Gravity.apply(gravity, outWidth, outHeight, potentialTaskRect, outRect);
     }
 
-    private static PointF getTaskDimension(Context context, DeviceProfile dp) {
+    private static PointF getTaskDimension(DeviceProfile dp) {
         PointF dimension = new PointF();
-        getTaskDimension(context, dp, dimension);
+        getTaskDimension(dp, dimension);
         return dimension;
     }
 
     /**
      * Gets the dimension of the task in the current system state.
      */
-    public static void getTaskDimension(Context context, DeviceProfile dp, PointF out) {
-        if (dp.isMultiWindowMode) {
-            WindowBounds bounds = SplitScreenBounds.INSTANCE.getSecondaryWindowBounds(context);
-            out.x = bounds.availableSize.x;
-            out.y = bounds.availableSize.y;
-            if (!TaskView.clipLeft(dp)) {
-                out.x += bounds.insets.left;
-            }
-            if (!TaskView.clipRight(dp)) {
-                out.x += bounds.insets.right;
-            }
-            if (!TaskView.clipTop(dp)) {
-                out.y += bounds.insets.top;
-            }
-            if (!TaskView.clipBottom(dp)) {
-                out.y += bounds.insets.bottom;
-            }
-        } else {
-            out.x = dp.widthPx;
-            out.y = dp.heightPx;
-            if (TaskView.clipLeft(dp)) {
-                out.x -= dp.getInsets().left;
-            }
-            if (TaskView.clipRight(dp)) {
-                out.x -= dp.getInsets().right;
-            }
-            if (TaskView.clipTop(dp)) {
-                out.y -= dp.getInsets().top;
-            }
-            if (TaskView.clipBottom(dp)) {
-                out.y -= Math.max(dp.getInsets().bottom, dp.taskbarSize);
-            }
+    public static void getTaskDimension(DeviceProfile dp, PointF out) {
+        out.x = dp.widthPx;
+        out.y = dp.heightPx;
+        if (TaskView.clipLeft(dp)) {
+            out.x -= dp.getInsets().left;
+        }
+        if (TaskView.clipRight(dp)) {
+            out.x -= dp.getInsets().right;
+        }
+        if (TaskView.clipTop(dp)) {
+            out.y -= dp.getInsets().top;
+        }
+        if (TaskView.clipBottom(dp)) {
+            out.y -= Math.max(dp.getInsets().bottom, dp.taskbarSize);
         }
     }
 
@@ -341,7 +321,7 @@
                 (taskRect.height() + dp.overviewTaskThumbnailTopMarginPx - dp.overviewRowSpacing)
                         / 2f;
 
-        PointF taskDimension = getTaskDimension(context, dp);
+        PointF taskDimension = getTaskDimension(dp);
         float scale = (rowHeight - dp.overviewTaskThumbnailTopMarginPx) / taskDimension.y;
         int outWidth = Math.round(scale * taskDimension.x);
         int outHeight = Math.round(scale * taskDimension.y);
diff --git a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index ee5bb44..99f7bdd 100644
--- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -23,16 +23,15 @@
 import static com.android.launcher3.GestureNavContract.EXTRA_ICON_SURFACE;
 import static com.android.launcher3.GestureNavContract.EXTRA_ON_FINISH_CALLBACK;
 import static com.android.launcher3.GestureNavContract.EXTRA_REMOTE_CALLBACK;
-import static com.android.launcher3.Utilities.createHomeIntent;
 import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
 import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.quickstep.OverviewComponentObserver.startHomeIntentSafely;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
 
 import android.animation.ObjectAnimator;
 import android.annotation.TargetApi;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityOptions;
-import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Matrix;
@@ -161,11 +160,7 @@
         if (gestureContractAnimationFactory != null && runningTaskTarget != null) {
             gestureContractAnimationFactory.addGestureContract(intent, runningTaskTarget.taskInfo);
         }
-        try {
-            mContext.startActivity(intent, options.toBundle());
-        } catch (NullPointerException | ActivityNotFoundException | SecurityException e) {
-            mContext.startActivity(createHomeIntent());
-        }
+        startHomeIntentSafely(mContext, intent, options.toBundle());
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/ImageActionsApi.java b/quickstep/src/com/android/quickstep/ImageActionsApi.java
index 154848d..2273806 100644
--- a/quickstep/src/com/android/quickstep/ImageActionsApi.java
+++ b/quickstep/src/com/android/quickstep/ImageActionsApi.java
@@ -78,16 +78,17 @@
         addImageAndSendIntent(crop, intent, true, exceptionCallback);
     }
 
-    @UiThread
     private void addImageAndSendIntent(@Nullable Rect crop, Intent intent, boolean setData,
             @Nullable Runnable exceptionCallback) {
-        if (mBitmapSupplier.get() == null) {
-            Log.e(TAG, "No snapshot available, not starting share.");
-            return;
-        }
 
-        UI_HELPER_EXECUTOR.execute(() -> persistBitmapAndStartActivity(mContext,
-                mBitmapSupplier.get(), crop, intent, (uri, intentForUri) -> {
+        UI_HELPER_EXECUTOR.execute(() -> {
+            Bitmap bitmap = mBitmapSupplier.get();
+            if (bitmap == null) {
+                Log.e(TAG, "No snapshot available, not starting share.");
+                return;
+            }
+            persistBitmapAndStartActivity(mContext,
+                    bitmap, crop, intent, (uri, intentForUri) -> {
                     intentForUri.addFlags(FLAG_GRANT_READ_URI_PERMISSION);
                     if (setData) {
                         intentForUri.setData(uri);
@@ -95,7 +96,8 @@
                         intentForUri.putExtra(EXTRA_STREAM, uri);
                     }
                     return new Intent[]{intentForUri};
-                }, TAG, exceptionCallback));
+                }, TAG, exceptionCallback);
+        });
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index 50d1244..196a664 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -91,7 +91,7 @@
             mActivity.setHintUserWillBeActive();
         }
 
-        if (!canUseWorkspaceView || appCanEnterPip || mIsSwipeForStagedSplit) {
+        if (!canUseWorkspaceView || appCanEnterPip || mIsSwipeForSplit) {
             return new LauncherHomeAnimationFactory();
         }
         if (workspaceView instanceof LauncherAppWidgetHostView) {
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index dffdc5a..1e7e89e 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -235,6 +235,10 @@
             }
         };
 
+        RecentsView<?, ?> visibleRecentsView = activityInterface.getVisibleRecentsView();
+        if (visibleRecentsView != null) {
+            visibleRecentsView.moveFocusedTaskToFront();
+        }
         if (mTaskAnimationManager.isRecentsAnimationRunning()) {
             cmd.mActiveCallbacks = mTaskAnimationManager.continueRecentsAnimation(gestureState);
             cmd.mActiveCallbacks.addListener(interactionHandler);
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index 0efe666..9e3173c 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -20,11 +20,11 @@
 import static android.content.Intent.ACTION_PACKAGE_CHANGED;
 import static android.content.Intent.ACTION_PACKAGE_REMOVED;
 
-import static com.android.launcher3.Utilities.createHomeIntent;
 import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY;
 import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
 import static com.android.systemui.shared.system.PackageManagerWrapper.ACTION_PREFERRED_ACTIVITY_CHANGED;
 
+import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -33,8 +33,12 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.os.Bundle;
 import android.util.SparseIntArray;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.tracing.OverviewComponentObserverProto;
 import com.android.launcher3.tracing.TouchInteractionServiceProto;
 import com.android.launcher3.util.SimpleBroadcastReceiver;
@@ -276,4 +280,34 @@
         overviewComponentObserver.setOverviewActivityResumed(mActivityInterface.isResumed());
         serviceProto.setOverviewComponentObvserver(overviewComponentObserver);
     }
+
+    /**
+     * Starts the intent for the current home activity.
+     */
+    public static void startHomeIntentSafely(@NonNull Context context, @Nullable Bundle options) {
+        RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState(context);
+        OverviewComponentObserver observer = new OverviewComponentObserver(context, deviceState);
+        Intent intent = observer.getHomeIntent();
+        observer.onDestroy();
+        deviceState.destroy();
+        startHomeIntentSafely(context, intent, options);
+    }
+
+    /**
+     * Starts the intent for the current home activity.
+     */
+    public static void startHomeIntentSafely(
+            @NonNull Context context, @NonNull Intent homeIntent, @Nullable Bundle options) {
+        try {
+            context.startActivity(homeIntent, options);
+        } catch (NullPointerException | ActivityNotFoundException | SecurityException e) {
+            context.startActivity(createHomeIntent(), options);
+        }
+    }
+
+    private static Intent createHomeIntent() {
+        return new Intent(Intent.ACTION_MAIN)
+                .addCategory(Intent.CATEGORY_HOME)
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 097850f..cd93dbe 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -34,7 +34,7 @@
 import com.android.systemui.shared.system.KeyguardManagerCompat;
 import com.android.wm.shell.recents.IRecentTasksListener;
 import com.android.wm.shell.util.GroupedRecentTaskInfo;
-import com.android.wm.shell.util.StagedSplitBounds;
+import com.android.wm.shell.util.SplitBounds;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -195,19 +195,19 @@
                                 tmpLockedUsers.get(task2Key.userId) /* isLocked */);
                 task2.setLastSnapshotData(taskInfo2);
             }
-            final SplitConfigurationOptions.StagedSplitBounds launcherSplitBounds =
-                    convertSplitBounds(rawTask.mStagedSplitBounds);
+            final SplitConfigurationOptions.SplitBounds launcherSplitBounds =
+                    convertSplitBounds(rawTask.mSplitBounds);
             allTasks.add(new GroupTask(task1, task2, launcherSplitBounds));
         }
 
         return allTasks;
     }
 
-    private SplitConfigurationOptions.StagedSplitBounds convertSplitBounds(
-            StagedSplitBounds shellSplitBounds) {
+    private SplitConfigurationOptions.SplitBounds convertSplitBounds(
+            SplitBounds shellSplitBounds) {
         return shellSplitBounds == null ?
                 null :
-                new SplitConfigurationOptions.StagedSplitBounds(
+                new SplitConfigurationOptions.SplitBounds(
                         shellSplitBounds.leftTopBounds, shellSplitBounds.rightBottomBounds,
                         shellSplitBounds.leftTopTaskId, shellSplitBounds.rightBottomTaskId);
     }
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 4f0b976..67ce606 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -21,11 +21,11 @@
 import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION;
 import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_DURATION;
 import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_PRE_DELAY;
-import static com.android.launcher3.Utilities.createHomeIntent;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.graphics.SysUiScrim.SYSUI_PROGRESS;
 import static com.android.launcher3.testing.TestProtocol.BAD_STATE;
 import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
+import static com.android.quickstep.OverviewComponentObserver.startHomeIntentSafely;
 import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
 import static com.android.quickstep.TaskViewUtils.createRecentsWindowAnimator;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
@@ -428,7 +428,7 @@
         RemoteAnimationAdapterCompat adapterCompat =
                 new RemoteAnimationAdapterCompat(runner, HOME_APPEAR_DURATION, 0,
                         getIApplicationThread());
-        startActivity(createHomeIntent(),
+        startHomeIntentSafely(this,
                 ActivityOptionsCompat.makeRemoteAnimation(adapterCompat).toBundle());
     }
 
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 2007ee1..b297973 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -104,8 +104,6 @@
         }
         if (mSplitScreenMinimized != splitScreenMinimized) {
             mSplitScreenMinimized = splitScreenMinimized;
-            UI_HELPER_EXECUTOR.execute(() -> SystemUiProxy.INSTANCE.get(context)
-                    .setSplitScreenMinimized(splitScreenMinimized));
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 1634c08..442578e 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -36,6 +36,7 @@
 import com.android.launcher3.util.Executors.SimpleThreadFactory;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.TaskVisualsChangeListener;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -54,7 +55,8 @@
  * Singleton class to load and manage recents model.
  */
 @TargetApi(Build.VERSION_CODES.O)
-public class RecentsModel implements IconChangeListener, TaskStackChangeListener {
+public class RecentsModel implements IconChangeListener, TaskStackChangeListener,
+        TaskVisualsChangeListener {
 
     // We do not need any synchronization for this variable as its only written on UI thread.
     public static final MainThreadInitializedObject<RecentsModel> INSTANCE =
@@ -77,6 +79,7 @@
 
         IconProvider iconProvider = new IconProvider(context);
         mIconCache = new TaskIconCache(context, RECENTS_MODEL_EXECUTOR, iconProvider);
+        mIconCache.registerTaskVisualsChangeListener(this);
         mThumbnailCache = new TaskThumbnailCache(context, RECENTS_MODEL_EXECUTOR);
 
         TaskStackChangeListeners.getInstance().registerTaskStackListener(this);
@@ -204,6 +207,13 @@
     }
 
     @Override
+    public void onTaskIconChanged(int taskId) {
+        for (TaskVisualsChangeListener listener : mThumbnailChangeListeners) {
+            listener.onTaskIconChanged(taskId);
+        }
+    }
+
+    @Override
     public void onSystemIconStateChanged(String iconState) {
         mIconCache.clearCache();
     }
@@ -226,20 +236,4 @@
         writer.println(prefix + "RecentsModel:");
         mTaskList.dump("  ", writer);
     }
-
-    /**
-     * Listener for receiving various task properties changes
-     */
-    public interface TaskVisualsChangeListener {
-
-        /**
-         * Called whn the task thumbnail changes
-         */
-        Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData);
-
-        /**
-         * Called when the icon for a task changes
-         */
-        void onTaskIconChanged(String pkg, UserHandle user);
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
index b20d488..1bd808d 100644
--- a/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
+++ b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
@@ -17,8 +17,6 @@
 
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 
-import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
-
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
 import java.util.ArrayList;
@@ -114,10 +112,6 @@
     }
 
     public void release() {
-        if (ENABLE_SHELL_TRANSITIONS) {
-            mReleaseChecks.clear();
-            return;
-        }
         if (mReleased) {
             return;
         }
diff --git a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
index c3ea256..7183c49 100644
--- a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
+++ b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
@@ -20,7 +20,7 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.util.TaskViewSimulator;
 import com.android.quickstep.util.TransformParams;
@@ -34,7 +34,7 @@
  */
 public class RemoteTargetGluer {
     private RemoteTargetHandle[] mRemoteTargetHandles;
-    private StagedSplitBounds mStagedSplitBounds;
+    private SplitBounds mSplitBounds;
 
     /**
      * Use this constructor if remote targets are split-screen independent
@@ -118,18 +118,18 @@
 
             // remoteTargetHandle[0] denotes topLeft task, so we pass in the bottomRight to exclude,
             // vice versa
-            mStagedSplitBounds = new StagedSplitBounds(
+            mSplitBounds = new SplitBounds(
                     topLeftTarget.startScreenSpaceBounds,
                     bottomRightTarget.startScreenSpaceBounds, splitIds[0], splitIds[1]);
             mRemoteTargetHandles[0].mTransformParams.setTargetSet(
                     createRemoteAnimationTargetsForTarget(targets, bottomRightTarget));
             mRemoteTargetHandles[0].mTaskViewSimulator.setPreview(topLeftTarget,
-                    mStagedSplitBounds);
+                    mSplitBounds);
 
             mRemoteTargetHandles[1].mTransformParams.setTargetSet(
                     createRemoteAnimationTargetsForTarget(targets, topLeftTarget));
             mRemoteTargetHandles[1].mTaskViewSimulator.setPreview(bottomRightTarget,
-                    mStagedSplitBounds);
+                    mSplitBounds);
         }
         return mRemoteTargetHandles;
     }
@@ -173,8 +173,8 @@
         return mRemoteTargetHandles;
     }
 
-    public StagedSplitBounds getStagedSplitBounds() {
-        return mStagedSplitBounds;
+    public SplitBounds getSplitBounds() {
+        return mSplitBounds;
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index 9667108..baeb514 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -17,7 +17,6 @@
 
 import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_SELECT;
 
 import android.animation.Animator;
 import android.content.Context;
@@ -72,16 +71,14 @@
     // How much further we can drag past recents, as a factor of mTransitionDragLength.
     protected float mDragLengthFactor = 1;
 
-    protected boolean mIsSwipeForStagedSplit;
+    protected boolean mIsSwipeForSplit;
 
     public SwipeUpAnimationLogic(Context context, RecentsAnimationDeviceState deviceState,
             GestureState gestureState) {
         mContext = context;
         mDeviceState = deviceState;
         mGestureState = gestureState;
-
-        mIsSwipeForStagedSplit = ENABLE_SPLIT_SELECT.get() &&
-                TopTaskTracker.INSTANCE.get(context).getRunningSplitTaskIds().length > 1;
+        mIsSwipeForSplit = TopTaskTracker.INSTANCE.get(context).getRunningSplitTaskIds().length > 1;
 
         mTargetGluer = new RemoteTargetGluer(mContext, mGestureState.getActivityInterface());
         mRemoteTargetHandles = mTargetGluer.getRemoteTargetHandles();
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 39d8b54..9a2619b 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -270,18 +270,6 @@
         }
     }
 
-    @Override
-    public Rect getNonMinimizedSplitScreenSecondaryBounds() {
-        if (mSystemUiProxy != null) {
-            try {
-                return mSystemUiProxy.getNonMinimizedSplitScreenSecondaryBounds();
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call getNonMinimizedSplitScreenSecondaryBounds", e);
-            }
-        }
-        return null;
-    }
-
     public float getLastNavButtonAlpha() {
         return mLastNavButtonAlpha;
     }
@@ -385,28 +373,6 @@
     }
 
     @Override
-    public void handleImageAsScreenshot(Bitmap bitmap, Rect rect, Insets insets, int i) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.handleImageAsScreenshot(bitmap, rect, insets, i);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call handleImageAsScreenshot", e);
-            }
-        }
-    }
-
-    @Override
-    public void setSplitScreenMinimized(boolean minimized) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.setSplitScreenMinimized(minimized);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call setSplitScreenMinimized", e);
-            }
-        }
-    }
-
-    @Override
     public void notifySwipeUpGestureStarted() {
         if (mSystemUiProxy != null) {
             try {
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 54f457d..fd7e367 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -195,7 +195,10 @@
                 } else if (nonAppTargets != null && nonAppTargets.length > 0) {
                     TaskViewUtils.createSplitAuxiliarySurfacesAnimator(
                             RemoteAnimationTargetCompat.wrap(nonAppTargets) /* nonApps */,
-                            true /*shown*/, dividerAnimator -> dividerAnimator.start());
+                            true /*shown*/, dividerAnimator -> {
+                                dividerAnimator.start();
+                                dividerAnimator.end();
+                            });
                 }
                 if (mController != null) {
                     if (mLastAppearedTaskTarget == null
diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java
index 3803f03..9ca5fc5 100644
--- a/quickstep/src/com/android/quickstep/TaskIconCache.java
+++ b/quickstep/src/com/android/quickstep/TaskIconCache.java
@@ -18,6 +18,7 @@
 import static com.android.launcher3.uioverrides.QuickstepLauncher.GO_LOW_RAM_RECENTS_ENABLED;
 import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
 
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManager.TaskDescription;
 import android.content.Context;
@@ -46,6 +47,7 @@
 import com.android.launcher3.util.Preconditions;
 import com.android.quickstep.util.CancellableTask;
 import com.android.quickstep.util.TaskKeyLruCache;
+import com.android.quickstep.util.TaskVisualsChangeListener;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.Task.TaskKey;
 import com.android.systemui.shared.system.PackageManagerWrapper;
@@ -70,6 +72,9 @@
 
     private BaseIconFactory mIconFactory;
 
+    @Nullable
+    public TaskVisualsChangeListener mTaskVisualsChangeListener = null;
+
     public TaskIconCache(Context context, Executor bgExecutor, IconProvider iconProvider) {
         mContext = context;
         mBgExecutor = bgExecutor;
@@ -116,6 +121,7 @@
                 task.icon = result.icon;
                 task.titleDescription = result.contentDescription;
                 callback.accept(task);
+                dispatchIconUpdate(task.key.id);
             }
         };
         mBgExecutor.execute(request);
@@ -272,4 +278,18 @@
         public Drawable icon;
         public String contentDescription = "";
     }
+
+    void registerTaskVisualsChangeListener(TaskVisualsChangeListener newListener) {
+        mTaskVisualsChangeListener = newListener;
+    }
+
+    void removeTaskVisualsChangeListener() {
+        mTaskVisualsChangeListener = null;
+    }
+
+    void dispatchIconUpdate(int taskId) {
+        if (mTaskVisualsChangeListener != null) {
+            mTaskVisualsChangeListener.onTaskIconChanged(taskId);
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index 3ef1332..e10cab6 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -23,7 +23,6 @@
 import static com.android.quickstep.views.OverviewActionsView.DISABLED_ROTATED;
 
 import android.annotation.SuppressLint;
-import android.app.ActivityManager;
 import android.content.Context;
 import android.graphics.Insets;
 import android.graphics.Matrix;
@@ -37,17 +36,12 @@
 
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.popup.SystemShortcut;
-import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.util.ResourceBasedOverride;
-import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
 import com.android.launcher3.views.ActivityContext;
-import com.android.quickstep.TaskShortcutFactory.SplitSelectSystemShortcut;
 import com.android.quickstep.util.RecentsOrientedState;
 import com.android.quickstep.views.OverviewActionsView;
 import com.android.quickstep.views.RecentsView;
@@ -66,7 +60,7 @@
 public class TaskOverlayFactory implements ResourceBasedOverride {
 
     public static List<SystemShortcut> getEnabledShortcuts(TaskView taskView,
-            DeviceProfile deviceProfile, TaskIdAttributeContainer taskContainer) {
+            TaskIdAttributeContainer taskContainer) {
         final ArrayList<SystemShortcut> shortcuts = new ArrayList<>();
         final BaseDraggingActivity activity = BaseActivity.fromContext(taskView.getContext());
         boolean hasMultipleTasks = taskView.getTaskIds()[1] != -1;
@@ -75,17 +69,11 @@
                 continue;
             }
 
-            SystemShortcut shortcut = menuOption.getShortcut(activity, taskContainer);
-            if (shortcut == null) {
+            List<SystemShortcut> menuShortcuts = menuOption.getShortcuts(activity, taskContainer);
+            if (menuShortcuts == null) {
                 continue;
             }
-
-            if (menuOption == TaskShortcutFactory.SPLIT_SCREEN &&
-                    FeatureFlags.ENABLE_SPLIT_SELECT.get()) {
-                addSplitOptions(shortcuts, activity, taskView, deviceProfile);
-            } else {
-                shortcuts.add(shortcut);
-            }
+            shortcuts.addAll(menuShortcuts);
         }
         RecentsOrientedState orientedState = taskView.getRecentsView().getPagedViewOrientedState();
         boolean canLauncherRotate = orientedState.isRecentsActivityRotationAllowed();
@@ -94,61 +82,24 @@
         // Add overview actions to the menu when in in-place rotate landscape mode.
         if (!canLauncherRotate && isInLandscape) {
             // Add screenshot action to task menu.
-            SystemShortcut screenshotShortcut = TaskShortcutFactory.SCREENSHOT
-                    .getShortcut(activity, taskContainer);
-            if (screenshotShortcut != null) {
-                shortcuts.add(screenshotShortcut);
+            List<SystemShortcut> screenshotShortcuts = TaskShortcutFactory.SCREENSHOT
+                    .getShortcuts(activity, taskContainer);
+            if (screenshotShortcuts != null) {
+                shortcuts.addAll(screenshotShortcuts);
             }
 
             // Add modal action only if display orientation is the same as the device orientation.
             if (orientedState.getDisplayRotation() == ROTATION_0) {
-                SystemShortcut modalShortcut = TaskShortcutFactory.MODAL
-                        .getShortcut(activity, taskContainer);
-                if (modalShortcut != null) {
-                    shortcuts.add(modalShortcut);
+                List<SystemShortcut> modalShortcuts = TaskShortcutFactory.MODAL
+                        .getShortcuts(activity, taskContainer);
+                if (modalShortcuts != null) {
+                    shortcuts.addAll(modalShortcuts);
                 }
             }
         }
         return shortcuts;
     }
 
-
-    /**
-     * Does NOT add split options in the following scenarios:
-     * * The taskView to add split options is already showing split screen tasks
-     * * There aren't at least 2 tasks in overview to show split options for
-     * * Device is in "Lock task mode"
-     * * The taskView to show split options for is the focused task AND we haven't started
-     * scrolling in overview (if we haven't scrolled, there's a split overview action button so
-     * we don't need this menu option)
-     */
-    private static void addSplitOptions(List<SystemShortcut> outShortcuts,
-            BaseDraggingActivity activity, TaskView taskView, DeviceProfile deviceProfile) {
-        RecentsView recentsView = taskView.getRecentsView();
-        PagedOrientationHandler orientationHandler = recentsView.getPagedOrientationHandler();
-        int[] taskViewTaskIds = taskView.getTaskIds();
-        boolean taskViewHasMultipleTasks = taskViewTaskIds[0] != -1 &&
-                taskViewTaskIds[1] != -1;
-        boolean notEnoughTasksToSplit = recentsView.getTaskViewCount() < 2;
-        boolean isFocusedTask = deviceProfile.isTablet && taskView.isFocusedTask();
-        boolean isTaskInExpectedScrollPosition =
-                recentsView.isTaskInExpectedScrollPosition(recentsView.indexOfChild(taskView));
-        ActivityManager activityManager =
-                (ActivityManager) taskView.getContext().getSystemService(Context.ACTIVITY_SERVICE);
-        boolean isLockTaskMode = activityManager.isInLockTaskMode();
-
-        if (taskViewHasMultipleTasks || notEnoughTasksToSplit || isLockTaskMode ||
-                (isFocusedTask && isTaskInExpectedScrollPosition)) {
-            return;
-        }
-
-        List<SplitPositionOption> positions =
-                orientationHandler.getSplitPositionOptions(deviceProfile);
-        for (SplitPositionOption option : positions) {
-            outShortcuts.add(new SplitSelectSystemShortcut(activity, taskView, option));
-        }
-    }
-
     public TaskOverlay createOverlay(TaskThumbnailView thumbnailView) {
         return new TaskOverlay(thumbnailView);
     }
@@ -170,7 +121,7 @@
     /** Note that these will be shown in order from top to bottom, if available for the task. */
     private static final TaskShortcutFactory[] MENU_OPTIONS = new TaskShortcutFactory[]{
             TaskShortcutFactory.APP_INFO,
-            TaskShortcutFactory.SPLIT_SCREEN,
+            TaskShortcutFactory.SPLIT_SELECT,
             TaskShortcutFactory.PIN,
             TaskShortcutFactory.INSTALL,
             TaskShortcutFactory.FREE_FORM,
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index e807e26..092854f 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -16,11 +16,8 @@
 
 package com.android.quickstep;
 
-import static android.view.Display.DEFAULT_DISPLAY;
-
 import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_SELECTIONS;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_SPLIT_SCREEN_TAP;
 
 import android.app.Activity;
 import android.app.ActivityOptions;
@@ -32,6 +29,8 @@
 import android.view.View;
 import android.window.SplashScreen;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
@@ -39,6 +38,7 @@
 import com.android.launcher3.model.WellbeingModel;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.popup.SystemShortcut.AppInfo;
+import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.util.InstantAppResolver;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
 import com.android.quickstep.views.RecentsView;
@@ -55,21 +55,35 @@
 
 import java.util.Collections;
 import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 
 /**
  * Represents a system shortcut that can be shown for a recent task.
  */
 public interface TaskShortcutFactory {
-    SystemShortcut getShortcut(BaseDraggingActivity activity,
-            TaskIdAttributeContainer taskContainer);
+    @Nullable
+    default List<SystemShortcut> getShortcuts(BaseDraggingActivity activity,
+            TaskIdAttributeContainer taskContainer) {
+        return null;
+    }
 
     default boolean showForSplitscreen() {
         return false;
     }
 
+    /** @return a singleton list if the provided shortcut is non-null, null otherwise */
+    @Nullable
+    default List<SystemShortcut> createSingletonShortcutList(@Nullable SystemShortcut shortcut) {
+        if (shortcut != null) {
+            return Collections.singletonList(shortcut);
+        }
+        return null;
+    }
+
     TaskShortcutFactory APP_INFO = new TaskShortcutFactory() {
         @Override
-        public SystemShortcut getShortcut(BaseDraggingActivity activity,
+        public List<SystemShortcut> getShortcuts(BaseDraggingActivity activity,
                 TaskIdAttributeContainer taskContainer) {
             TaskView taskView = taskContainer.getTaskView();
             AppInfo.SplitAccessibilityInfo accessibilityInfo =
@@ -77,7 +91,8 @@
                             TaskUtils.getTitle(taskView.getContext(), taskContainer.getTask()),
                             taskContainer.getA11yNodeId()
                     );
-            return new AppInfo(activity, taskContainer.getItemInfo(), taskView, accessibilityInfo);
+            return Collections.singletonList(new AppInfo(activity, taskContainer.getItemInfo(),
+                    taskView, accessibilityInfo));
         }
 
         @Override
@@ -103,7 +118,7 @@
         protected abstract boolean onActivityStarted(BaseDraggingActivity activity);
 
         @Override
-        public SystemShortcut getShortcut(BaseDraggingActivity activity,
+        public List<SystemShortcut> getShortcuts(BaseDraggingActivity activity,
                 TaskIdAttributeContainer taskContainer) {
             final Task task  = taskContainer.getTask();
             if (!task.isDockable) {
@@ -112,8 +127,8 @@
             if (!isAvailable(activity, task.key.displayId)) {
                 return null;
             }
-            return new MultiWindowSystemShortcut(mIconRes, mTextRes, activity, taskContainer, this,
-                    mLauncherEvent);
+            return Collections.singletonList(new MultiWindowSystemShortcut(mIconRes,
+                    mTextRes, activity, taskContainer, this, mLauncherEvent));
         }
     }
 
@@ -242,34 +257,50 @@
         }
     }
 
-    /** @Deprecated */
-    TaskShortcutFactory SPLIT_SCREEN = new MultiWindowFactory(R.drawable.ic_split_screen,
-            R.string.recent_task_option_split_screen, LAUNCHER_SYSTEM_SHORTCUT_SPLIT_SCREEN_TAP) {
-
+    /**
+     * Does NOT add split options in the following scenarios:
+     * * The taskView to add split options is already showing split screen tasks
+     * * There aren't at least 2 tasks in overview to show split options for
+     * * Split isn't supported by the task itself (non resizable activity)
+     * * We aren't currently in multi-window
+     * * The taskView to show split options for is the focused task AND we haven't started
+     * scrolling in overview (if we haven't scrolled, there's a split overview action button so
+     * we don't need this menu option)
+     */
+    TaskShortcutFactory SPLIT_SELECT = new TaskShortcutFactory() {
         @Override
-        protected boolean isAvailable(BaseDraggingActivity activity, int displayId) {
-            // Don't show menu-item if already in multi-window and the task is from
-            // the secondary display.
-            // TODO(b/118266305): Temporarily disable splitscreen for secondary display while new
-            // implementation is enabled
-            return !activity.getDeviceProfile().isMultiWindowMode
-                    && (displayId == -1 || displayId == DEFAULT_DISPLAY);
-        }
+        public List<SystemShortcut> getShortcuts(BaseDraggingActivity activity,
+                TaskIdAttributeContainer taskContainer) {
+            DeviceProfile deviceProfile = activity.getDeviceProfile();
+            final Task task  = taskContainer.getTask();
+            final TaskView taskView = taskContainer.getTaskView();
+            final RecentsView recentsView = taskView.getRecentsView();
+            final PagedOrientationHandler orientationHandler =
+                    recentsView.getPagedOrientationHandler();
 
-        @Override
-        protected ActivityOptions makeLaunchOptions(Activity activity) {
-            final int navBarPosition = WindowManagerWrapper.getInstance().getNavBarPosition(
-                    activity.getDisplayId());
-            if (navBarPosition == WindowManagerWrapper.NAV_BAR_POS_INVALID) {
+            int[] taskViewTaskIds = taskView.getTaskIds();
+            boolean taskViewHasMultipleTasks = taskViewTaskIds[0] != -1 &&
+                    taskViewTaskIds[1] != -1;
+            boolean notEnoughTasksToSplit = recentsView.getTaskViewCount() < 2;
+            boolean isFocusedTask = deviceProfile.isTablet && taskView.isFocusedTask();
+            boolean isTaskInExpectedScrollPosition =
+                    recentsView.isTaskInExpectedScrollPosition(recentsView.indexOfChild(taskView));
+            boolean isTaskSplitNotSupported = !task.isDockable;
+            boolean hideForExistingMultiWindow = activity.getDeviceProfile().isMultiWindowMode;
+
+            if (taskViewHasMultipleTasks ||
+                    notEnoughTasksToSplit ||
+                    isTaskSplitNotSupported ||
+                    hideForExistingMultiWindow ||
+                    (isFocusedTask && isTaskInExpectedScrollPosition)) {
                 return null;
             }
-            boolean dockTopOrLeft = navBarPosition != WindowManagerWrapper.NAV_BAR_POS_LEFT;
-            return ActivityOptionsCompat.makeSplitScreenOptions(dockTopOrLeft);
-        }
 
-        @Override
-        protected boolean onActivityStarted(BaseDraggingActivity activity) {
-            return true;
+            return orientationHandler.getSplitPositionOptions(deviceProfile)
+                    .stream()
+                    .map((Function<SplitPositionOption, SystemShortcut>) option ->
+                            new SplitSelectSystemShortcut(activity, taskView, option))
+                    .collect(Collectors.toList());
         }
     };
 
@@ -297,18 +328,22 @@
         }
     };
 
-    TaskShortcutFactory PIN = (activity, taskContainer) -> {
-        if (!SystemUiProxy.INSTANCE.get(activity).isActive()) {
-            return null;
+    TaskShortcutFactory PIN = new TaskShortcutFactory() {
+        @Override
+        public List<SystemShortcut> getShortcuts(BaseDraggingActivity activity,
+                TaskIdAttributeContainer taskContainer) {
+            if (!SystemUiProxy.INSTANCE.get(activity).isActive()) {
+                return null;
+            }
+            if (!ActivityManagerWrapper.getInstance().isScreenPinningEnabled()) {
+                return null;
+            }
+            if (ActivityManagerWrapper.getInstance().isLockToAppActive()) {
+                // We shouldn't be able to pin while an app is locked.
+                return null;
+            }
+            return Collections.singletonList(new PinSystemShortcut(activity, taskContainer));
         }
-        if (!ActivityManagerWrapper.getInstance().isScreenPinningEnabled()) {
-            return null;
-        }
-        if (ActivityManagerWrapper.getInstance().isLockToAppActive()) {
-            // We shouldn't be able to pin while an app is locked.
-            return null;
-        }
-        return new PinSystemShortcut(activity, taskContainer);
     };
 
     class PinSystemShortcut extends SystemShortcut<BaseDraggingActivity> {
@@ -335,26 +370,52 @@
         }
     }
 
-    TaskShortcutFactory INSTALL = (activity, taskContainer) ->
-            InstantAppResolver.newInstance(activity).isInstantApp(activity,
-                    taskContainer.getTask().getTopComponent().getPackageName())
-                    ? new SystemShortcut.Install(activity, taskContainer.getItemInfo(),
-                    taskContainer.getTaskView()) : null;
+    TaskShortcutFactory INSTALL = new TaskShortcutFactory() {
+        @Override
+        public List<SystemShortcut> getShortcuts(BaseDraggingActivity activity,
+                TaskIdAttributeContainer taskContainer) {
+            return InstantAppResolver.newInstance(activity).isInstantApp(activity,
+                    taskContainer.getTask().getTopComponent().getPackageName()) ?
+                    Collections.singletonList(new SystemShortcut.Install(activity,
+                            taskContainer.getItemInfo(), taskContainer.getTaskView())) :
+                    null;
+        }
+    };
 
-    TaskShortcutFactory WELLBEING = (activity, taskContainer) ->
-            WellbeingModel.SHORTCUT_FACTORY.getShortcut(activity, taskContainer.getItemInfo(),
-                    taskContainer.getTaskView());
+    TaskShortcutFactory WELLBEING = new TaskShortcutFactory() {
+        @Override
+        public List<SystemShortcut> getShortcuts(BaseDraggingActivity activity,
+                TaskIdAttributeContainer taskContainer) {
+            SystemShortcut<BaseDraggingActivity> wellbeingShortcut =
+                    WellbeingModel.SHORTCUT_FACTORY.getShortcut(activity,
+                            taskContainer.getItemInfo(), taskContainer.getTaskView());
+            return createSingletonShortcutList(wellbeingShortcut);
+        }
+    };
 
-    TaskShortcutFactory SCREENSHOT = (activity, taskContainer) ->
-            taskContainer.getThumbnailView().getTaskOverlay()
+    TaskShortcutFactory SCREENSHOT = new TaskShortcutFactory() {
+        @Override
+        public List<SystemShortcut> getShortcuts(BaseDraggingActivity activity,
+                TaskIdAttributeContainer taskContainer) {
+            SystemShortcut screenshotShortcut = taskContainer.getThumbnailView().getTaskOverlay()
                     .getScreenshotShortcut(activity, taskContainer.getItemInfo(),
                             taskContainer.getTaskView());
-
-    TaskShortcutFactory MODAL = (activity, taskContainer) -> {
-        if (ENABLE_OVERVIEW_SELECTIONS.get()) {
-            return taskContainer.getThumbnailView().getTaskOverlay().getModalStateSystemShortcut(
-                    taskContainer.getItemInfo(), taskContainer.getTaskView());
+            return createSingletonShortcutList(screenshotShortcut);
         }
-        return null;
+    };
+
+    TaskShortcutFactory MODAL = new TaskShortcutFactory() {
+        @Override
+        public List<SystemShortcut> getShortcuts(BaseDraggingActivity activity,
+                TaskIdAttributeContainer taskContainer) {
+            SystemShortcut modalStateSystemShortcut =
+                    taskContainer.getThumbnailView().getTaskOverlay()
+                            .getModalStateSystemShortcut(
+                                    taskContainer.getItemInfo(), taskContainer.getTaskView());
+            if (ENABLE_OVERVIEW_SELECTIONS.get()) {
+                return createSingletonShortcutList(modalStateSystemShortcut);
+            }
+            return null;
+        }
     };
 }
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index db402af..a030568 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -468,7 +468,6 @@
             animatorSet.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
-                    super.onAnimationEnd(animation);
                     finishCallback.run();
                 }
             });
@@ -530,7 +529,6 @@
                 for (SurfaceControl leash: closingTargets) {
                     t.hide(leash);
                 }
-                super.onAnimationEnd(animation);
                 finishCallback.run();
             }
         });
@@ -599,9 +597,14 @@
             launcherAnim.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
             launcherAnim.setDuration(RECENTS_LAUNCH_DURATION);
 
-            // Make sure recents gets fixed up by resetting task alphas and scales, etc.
             windowAnimEndListener = new AnimatorListenerAdapter() {
                 @Override
+                public void onAnimationStart(Animator animation) {
+                    recentsView.onTaskLaunchedInLiveTileMode();
+                }
+
+                // Make sure recents gets fixed up by resetting task alphas and scales, etc.
+                @Override
                 public void onAnimationEnd(Animator animation) {
                     recentsView.finishRecentsAnimation(false /* toRecents */, () -> {
                         recentsView.post(() -> {
@@ -677,7 +680,6 @@
         dockFadeAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
-                super.onAnimationStart(animation);
                 if (shown) {
                     for (SurfaceControl leash : auxiliarySurfaces) {
                         t.setAlpha(leash, 0);
@@ -689,7 +691,6 @@
 
             @Override
             public void onAnimationEnd(Animator animation) {
-                super.onAnimationEnd(animation);
                 if (!shown) {
                     for (SurfaceControl leash : auxiliarySurfaces) {
                         t.hide(leash);
diff --git a/quickstep/src/com/android/quickstep/TopTaskTracker.java b/quickstep/src/com/android/quickstep/TopTaskTracker.java
index 723dc72..42fa86d 100644
--- a/quickstep/src/com/android/quickstep/TopTaskTracker.java
+++ b/quickstep/src/com/android/quickstep/TopTaskTracker.java
@@ -33,7 +33,7 @@
 import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
 import com.android.launcher3.util.SplitConfigurationOptions.StageType;
-import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitTaskPosition;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitStageInfo;
 import com.android.launcher3.util.TraceHelper;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.Task.TaskKey;
@@ -63,8 +63,9 @@
     // Ordered list with first item being the most recent task.
     private final LinkedList<RunningTaskInfo> mOrderedTaskList = new LinkedList<>();
 
-    private final StagedSplitTaskPosition mMainStagePosition = new StagedSplitTaskPosition();
-    private final StagedSplitTaskPosition mSideStagePosition = new StagedSplitTaskPosition();
+
+    private final SplitStageInfo mMainStagePosition = new SplitStageInfo();
+    private final SplitStageInfo mSideStagePosition = new SplitStageInfo();
     private int mPinnedTaskId = INVALID_TASK_ID;
 
     private TopTaskTracker(Context context) {
@@ -144,8 +145,8 @@
         mPinnedTaskId = INVALID_TASK_ID;
     }
 
-    private void resetTaskId(StagedSplitTaskPosition taskPosition) {
-        taskPosition.taskId = INVALID_TASK_ID;
+    private void resetTaskId(SplitStageInfo taskPosition) {
+        taskPosition.taskId = -1;
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 61d36fb..bf1c998 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -51,6 +51,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.util.Log;
 import android.view.Choreographer;
 import android.view.InputEvent;
@@ -127,6 +128,9 @@
 
     private static final String TAG = "TouchInteractionService";
 
+    private static final boolean BUBBLES_HOME_GESTURE_ENABLED =
+            SystemProperties.getBoolean("persist.wm.debug.bubbles_home_gesture", true);
+
     private static final String KEY_BACK_NOTIFICATION_COUNT = "backNotificationCount";
     private static final String NOTIFY_ACTION_BACK = "com.android.quickstep.action.BACK_GESTURE";
     private static final String HAS_ENABLED_QUICKSTEP_ONCE = "launcher.has_enabled_quickstep_once";
@@ -723,16 +727,30 @@
                 base = new TaskbarStashInputConsumer(this, base, mInputMonitorCompat, tac);
             }
 
-            // If Bubbles is expanded, use the overlay input consumer, which will close Bubbles
-            // instead of going all the way home when a swipe up is detected.
-            // Notification panel can be expanded on top of expanded bubbles. Bubbles remain
-            // expanded in the back. Make sure swipe up is not passed to bubbles in this case.
-            if ((mDeviceState.isBubblesExpanded() && !mDeviceState.isNotificationPanelExpanded())
-                    || mDeviceState.isSystemUiDialogShowing()) {
+            if (mDeviceState.isBubblesExpanded()) {
+                if (BUBBLES_HOME_GESTURE_ENABLED) {
+                    // Bubbles can handle home gesture itself.
+                    base = getDefaultInputConsumer();
+                } else {
+                    // If Bubbles is expanded, use the overlay input consumer, which will close
+                    // Bubbles instead of going all the way home when a swipe up is detected.
+                    // Notification panel can be expanded on top of expanded bubbles. Bubbles remain
+                    // expanded in the back. Make sure swipe up is not passed to bubbles in this
+                    // case.
+                    if (!mDeviceState.isNotificationPanelExpanded()) {
+                        base = new SysUiOverlayInputConsumer(
+                                getBaseContext(), mDeviceState, mInputMonitorCompat);
+                    }
+                }
+            }
+
+            if (mDeviceState.isSystemUiDialogShowing()) {
                 base = new SysUiOverlayInputConsumer(
                         getBaseContext(), mDeviceState, mInputMonitorCompat);
             }
 
+
+
             if (mDeviceState.isScreenPinningActive()) {
                 // Note: we only allow accessibility to wrap this, and it replaces the previous
                 // base input consumer (which should be NO_OP anyway since topTaskLocked == true).
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsState.java b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
index 77db6b7..af9d0cb 100644
--- a/quickstep/src/com/android/quickstep/fallback/RecentsState.java
+++ b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
@@ -15,6 +15,7 @@
  */
 package com.android.quickstep.fallback;
 
+import static com.android.launcher3.LauncherState.FLAG_CLOSE_POPUPS;
 import static com.android.launcher3.uioverrides.states.BackgroundAppState.getOverviewScaleAndOffsetForBackgroundState;
 import static com.android.launcher3.uioverrides.states.OverviewModalTaskState.getOverviewScaleAndOffsetForModalState;
 
@@ -52,7 +53,7 @@
     public static final RecentsState HOME = new RecentsState(3, 0);
     public static final RecentsState BG_LAUNCHER = new LauncherState(4, 0);
     public static final RecentsState OVERVIEW_SPLIT_SELECT = new RecentsState(5,
-            FLAG_SHOW_AS_GRID | FLAG_SCRIM | FLAG_OVERVIEW_UI);
+            FLAG_SHOW_AS_GRID | FLAG_SCRIM | FLAG_OVERVIEW_UI | FLAG_CLOSE_POPUPS);
 
     public final int ordinal;
     private final int mFlags;
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
index 3d737ca..c1750b5 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
@@ -19,12 +19,13 @@
 import static android.view.MotionEvent.ACTION_POINTER_DOWN;
 import static android.view.MotionEvent.ACTION_UP;
 
-import static com.android.launcher3.Utilities.createHomeIntent;
 import static com.android.launcher3.Utilities.squaredHypot;
 import static com.android.launcher3.Utilities.squaredTouchSlop;
 import static com.android.launcher3.util.VelocityUtils.PX_PER_MS;
 import static com.android.quickstep.AbsSwipeUpHandler.MIN_PROGRESS_FOR_OVERVIEW;
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
+import static com.android.quickstep.OverviewComponentObserver.startHomeIntentSafely;
+import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
 import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
 
 import android.animation.Animator;
@@ -101,6 +102,8 @@
 
     private boolean mThresholdCrossed = false;
     private boolean mHomeLaunched = false;
+    private boolean mCancelWhenRecentsStart = false;
+    private boolean mDismissTask = false;
 
     private RecentsAnimationController mRecentsAnimationController;
 
@@ -204,9 +207,20 @@
             animator.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
-                    if (dismissTask) {
-                        // For now, just start the home intent so user is prompted to unlock the device.
-                        mContext.startActivity(createHomeIntent());
+                    if (ENABLE_SHELL_TRANSITIONS) {
+                        if (mTaskAnimationManager.getCurrentCallbacks() != null) {
+                            if (mRecentsAnimationController != null) {
+                                finishRecentsAnimationForShell(dismissTask);
+                            } else {
+                                // the transition of recents animation hasn't started, wait for it
+                                mCancelWhenRecentsStart = true;
+                                mDismissTask = dismissTask;
+                            }
+                        }
+                    } else if (dismissTask) {
+                        // For now, just start the home intent so user is prompted to
+                        // unlock the device.
+                        startHomeIntentSafely(mContext, mGestureState.getHomeIntent(), null);
                         mHomeLaunched = true;
                     }
                     mStateCallback.setState(STATE_HANDLER_INVALIDATED);
@@ -238,12 +252,24 @@
         mTransformParams.setTargetSet(targets);
         applyTransform();
         mStateCallback.setState(STATE_TARGET_RECEIVED);
+        if (mCancelWhenRecentsStart) {
+            finishRecentsAnimationForShell(mDismissTask);
+        }
     }
 
     @Override
     public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
         mRecentsAnimationController = null;
         mTransformParams.setTargetSet(null);
+        mCancelWhenRecentsStart = false;
+    }
+
+    private void finishRecentsAnimationForShell(boolean dismissTask) {
+        mCancelWhenRecentsStart = false;
+        mTaskAnimationManager.finishRunningRecentsAnimation(dismissTask /* toHome */);
+        if (dismissTask) {
+            mHomeLaunched = true;
+        }
     }
 
     private void endRemoteAnimation() {
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index e458c1f..641385d 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -27,6 +27,7 @@
 import static com.android.launcher3.PagedView.DEBUG_FAILED_QUICKSWITCH;
 import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
 import static com.android.launcher3.Utilities.squaredHypot;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS;
 import static com.android.launcher3.util.VelocityUtils.PX_PER_MS;
 import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
@@ -47,6 +48,7 @@
 import androidx.annotation.UiThread;
 
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.tracing.InputConsumerProto;
@@ -58,7 +60,9 @@
 import com.android.quickstep.GestureState;
 import com.android.quickstep.InputConsumer;
 import com.android.quickstep.RecentsAnimationCallbacks;
+import com.android.quickstep.RecentsAnimationController;
 import com.android.quickstep.RecentsAnimationDeviceState;
+import com.android.quickstep.RecentsAnimationTargets;
 import com.android.quickstep.RotationTouchHelper;
 import com.android.quickstep.TaskAnimationManager;
 import com.android.quickstep.util.ActiveGestureLog;
@@ -107,6 +111,7 @@
     private VelocityTracker mVelocityTracker;
 
     private AbsSwipeUpHandler mInteractionHandler;
+    private final FinishImmediatelyHandler mCleanupHandler = new FinishImmediatelyHandler();
 
     private final boolean mIsDeferredDownTarget;
     private final PointF mDownPos = new PointF();
@@ -377,6 +382,7 @@
 
         if (mTaskAnimationManager.isRecentsAnimationRunning()) {
             mActiveCallbacks = mTaskAnimationManager.continueRecentsAnimation(mGestureState);
+            mActiveCallbacks.removeListener(mCleanupHandler);
             mActiveCallbacks.addListener(mInteractionHandler);
             mTaskAnimationManager.notifyRecentsAnimationState(mInteractionHandler);
             notifyGestureStarted(true /*isLikelyToStartNewTask*/);
@@ -414,7 +420,19 @@
             }
         } else {
             // Since we start touch tracking on DOWN, we may reach this state without actually
-            // starting the gesture. In that case, just cleanup immediately.
+            // starting the gesture. In that case, we need to clean-up an unfinished or un-started
+            // animation.
+            if (mActiveCallbacks != null && mInteractionHandler != null) {
+                if (mTaskAnimationManager.isRecentsAnimationRunning()) {
+                    // The animation started, but with no movement, in this case, there will be no
+                    // animateToProgress so we have to manually finish here.
+                    mTaskAnimationManager.finishRunningRecentsAnimation(false /* toHome */);
+                } else {
+                    // The animation hasn't started yet, so insert a replacement handler into the
+                    // callbacks which immediately finishes the animation after it starts.
+                    mActiveCallbacks.addListener(mCleanupHandler);
+                }
+            }
             onConsumerAboutToBeSwitched();
             onInteractionGestureFinished();
 
@@ -464,7 +482,7 @@
     }
 
     private void removeListener() {
-        if (mActiveCallbacks != null) {
+        if (mActiveCallbacks != null && mInteractionHandler != null) {
             mActiveCallbacks.removeListener(mInteractionHandler);
         }
     }
@@ -490,4 +508,19 @@
             mInteractionHandler.writeToProto(inputConsumerProto);
         }
     }
+
+    /**
+     * A listener which just finishes the animation immediately after starting. Replaces
+     * AbsSwipeUpHandler if the gesture itself finishes before the animation even starts.
+     */
+    private static class FinishImmediatelyHandler
+            implements RecentsAnimationCallbacks.RecentsAnimationListener {
+
+        public void onRecentsAnimationStart(RecentsAnimationController controller,
+                RecentsAnimationTargets targets) {
+            Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
+                controller.finish(false /* toRecents */, null);
+            });
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
index 864e08d..a730183 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
@@ -15,12 +15,11 @@
  */
 package com.android.quickstep.inputconsumers;
 
-import static com.android.launcher3.Utilities.createHomeIntent;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
+import static com.android.quickstep.OverviewComponentObserver.startHomeIntentSafely;
 
-import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.graphics.PointF;
 import android.view.MotionEvent;
@@ -79,11 +78,7 @@
 
     @Override
     public void onSwipeUp(boolean wasFling, PointF finalVelocity) {
-        try {
-            mContext.startActivity(mGestureState.getHomeIntent());
-        } catch (NullPointerException | ActivityNotFoundException | SecurityException e) {
-            mContext.startActivity(createHomeIntent());
-        }
+        startHomeIntentSafely(mContext, mGestureState.getHomeIntent(), null);
         ActiveGestureLog.INSTANCE.addLog("startQuickstep");
         BaseActivity activity = BaseDraggingActivity.fromContext(mContext);
         int state = (mGestureState != null && mGestureState.getEndTarget() != null)
diff --git a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
index 5680170..aa52789 100644
--- a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
@@ -19,6 +19,7 @@
 import static com.android.launcher3.Utilities.mapRange;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.quickstep.OverviewComponentObserver.startHomeIntentSafely;
 
 import android.animation.Animator;
 import android.app.Activity;
@@ -148,45 +149,50 @@
     }
 
     private void startBackgroundAnimation() {
-        if (Utilities.ATLEAST_S && mVibrator != null && mVibrator.areAllPrimitivesSupported(
-                VibrationEffect.Composition.PRIMITIVE_THUD)) {
-            if (mBackgroundAnimatorListener == null) {
-                mBackgroundAnimatorListener =
-                        new Animator.AnimatorListener() {
-                            @Override
-                            public void onAnimationStart(Animator animation) {
-                                runOnUiHelperThread(() -> mVibrator.vibrate(getVibrationEffect()));
-                            }
-
-                            @Override
-                            public void onAnimationRepeat(Animator animation) {
-                                runOnUiHelperThread(() -> mVibrator.vibrate(getVibrationEffect()));
-                            }
-
-                            @Override
-                            public void onAnimationEnd(Animator animation) {
-                                runOnUiHelperThread(mVibrator::cancel);
-                            }
-
-                            @Override
-                            public void onAnimationCancel(Animator animation) {
-                                runOnUiHelperThread(mVibrator::cancel);
-                            }
-                        };
-            }
-            mAnimatedBackground.addAnimatorListener(mBackgroundAnimatorListener);
+        if (!Utilities.ATLEAST_S || mVibrator == null) {
+            return;
         }
-        mAnimatedBackground.playAnimation();
-    }
+        boolean supportsThud = mVibrator.areAllPrimitivesSupported(
+                VibrationEffect.Composition.PRIMITIVE_THUD);
 
-    /**
-     * Sets up the vibration effect for the next round of animation. The parameters vary between
-     * different illustrations.
-     */
-    private VibrationEffect getVibrationEffect() {
-        return VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD, 1.0f, 50)
-                .compose();
+        if (!supportsThud && !mVibrator.areAllPrimitivesSupported(
+                VibrationEffect.Composition.PRIMITIVE_TICK)) {
+            return;
+        }
+        if (mBackgroundAnimatorListener == null) {
+            VibrationEffect vibrationEffect = VibrationEffect.startComposition()
+                    .addPrimitive(supportsThud
+                                    ? VibrationEffect.Composition.PRIMITIVE_THUD
+                                    : VibrationEffect.Composition.PRIMITIVE_TICK,
+                            /* scale= */ 1.0f,
+                            /* delay= */ 50)
+                    .compose();
+
+            mBackgroundAnimatorListener =
+                    new Animator.AnimatorListener() {
+                        @Override
+                        public void onAnimationStart(Animator animation) {
+                            runOnUiHelperThread(() -> mVibrator.vibrate(vibrationEffect));
+                        }
+
+                        @Override
+                        public void onAnimationRepeat(Animator animation) {
+                            runOnUiHelperThread(() -> mVibrator.vibrate(vibrationEffect));
+                        }
+
+                        @Override
+                        public void onAnimationEnd(Animator animation) {
+                            runOnUiHelperThread(mVibrator::cancel);
+                        }
+
+                        @Override
+                        public void onAnimationCancel(Animator animation) {
+                            runOnUiHelperThread(mVibrator::cancel);
+                        }
+                    };
+        }
+        mAnimatedBackground.addAnimatorListener(mBackgroundAnimatorListener);
+        mAnimatedBackground.playAnimation();
     }
 
     @Override
@@ -284,7 +290,7 @@
         @Override
         public boolean performAccessibilityAction(View host, int action, Bundle args) {
             if (action == AccessibilityAction.ACTION_CLICK.getId()) {
-                startActivity(Utilities.createHomeIntent());
+                startHomeIntentSafely(AllSetActivity.this, null);
                 finish();
                 return true;
             }
diff --git a/quickstep/src/com/android/quickstep/util/GroupTask.java b/quickstep/src/com/android/quickstep/util/GroupTask.java
index e2563e3..f30d00c 100644
--- a/quickstep/src/com/android/quickstep/util/GroupTask.java
+++ b/quickstep/src/com/android/quickstep/util/GroupTask.java
@@ -19,7 +19,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
 import com.android.systemui.shared.recents.model.Task;
 
 /**
@@ -29,13 +29,14 @@
 public class GroupTask {
     public @NonNull Task task1;
     public @Nullable Task task2;
-    public @Nullable StagedSplitBounds mStagedSplitBounds;
+    public @Nullable
+    SplitBounds mSplitBounds;
 
     public GroupTask(@NonNull Task t1, @Nullable Task t2,
-            @Nullable StagedSplitBounds stagedSplitBounds) {
+            @Nullable SplitBounds splitBounds) {
         task1 = t1;
         task2 = t2;
-        mStagedSplitBounds = stagedSplitBounds;
+        mSplitBounds = splitBounds;
     }
 
     public GroupTask(@NonNull GroupTask group) {
@@ -43,7 +44,7 @@
         task2 = group.task2 != null
                 ? new Task(group.task2)
                 : null;
-        mStagedSplitBounds = group.mStagedSplitBounds;
+        mSplitBounds = group.mSplitBounds;
     }
 
     public boolean containsTask(int taskId) {
diff --git a/quickstep/src/com/android/quickstep/util/ImageActionUtils.java b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
index 63d5b0d..9fe24de 100644
--- a/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
+++ b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
@@ -43,7 +43,6 @@
 import android.util.Log;
 import android.view.View;
 
-import androidx.annotation.UiThread;
 import androidx.annotation.WorkerThread;
 import androidx.core.content.FileProvider;
 
@@ -86,67 +85,70 @@
      * Launch the activity to share image for overview sharing. This is to share cropped bitmap
      * with specific share targets (with shortcutInfo and appTarget) rendered in overview.
      */
-    @UiThread
     public static void shareImage(Context context, Supplier<Bitmap> bitmapSupplier, RectF rectF,
             ShortcutInfo shortcutInfo, AppTarget appTarget, String tag) {
-        if (bitmapSupplier.get() == null) {
-            return;
-        }
-        Rect crop = new Rect();
-        rectF.round(crop);
-        Intent intent = new Intent();
-        Uri uri =  getImageUri(bitmapSupplier.get(), crop, context, tag);
-        ClipData clipdata = new ClipData(new ClipDescription("content",
-                new String[]{"image/png"}),
-                new ClipData.Item(uri));
-        intent.setAction(Intent.ACTION_SEND)
-            .setComponent(new ComponentName(appTarget.getPackageName(), appTarget.getClassName()))
-            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-            .addFlags(FLAG_GRANT_READ_URI_PERMISSION)
-            .setType("image/png")
-            .putExtra(Intent.EXTRA_STREAM, uri)
-            .putExtra(Intent.EXTRA_SHORTCUT_ID, shortcutInfo.getId())
-            .setClipData(clipdata);
+        UI_HELPER_EXECUTOR.execute(() -> {
+            Bitmap bitmap = bitmapSupplier.get();
+            if (bitmap == null) {
+                return;
+            }
+            Rect crop = new Rect();
+            rectF.round(crop);
+            Intent intent = new Intent();
+            Uri uri = getImageUri(bitmap, crop, context, tag);
+            ClipData clipdata = new ClipData(new ClipDescription("content",
+                    new String[]{"image/png"}),
+                    new ClipData.Item(uri));
+            intent.setAction(Intent.ACTION_SEND)
+                    .setComponent(
+                            new ComponentName(appTarget.getPackageName(), appTarget.getClassName()))
+                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                    .addFlags(FLAG_GRANT_READ_URI_PERMISSION)
+                    .setType("image/png")
+                    .putExtra(Intent.EXTRA_STREAM, uri)
+                    .putExtra(Intent.EXTRA_SHORTCUT_ID, shortcutInfo.getId())
+                    .setClipData(clipdata);
 
-        if (context.getUserId() != appTarget.getUser().getIdentifier()) {
-            intent.prepareToLeaveUser(context.getUserId());
-            intent.fixUris(context.getUserId());
-            context.startActivityAsUser(intent, appTarget.getUser());
-        } else {
-            context.startActivity(intent);
-        }
+            if (context.getUserId() != appTarget.getUser().getIdentifier()) {
+                intent.prepareToLeaveUser(context.getUserId());
+                intent.fixUris(context.getUserId());
+                context.startActivityAsUser(intent, appTarget.getUser());
+            } else {
+                context.startActivity(intent);
+            }
+        });
     }
 
     /**
      * Launch the activity to share image.
      */
-    @UiThread
     public static void startShareActivity(Context context, Supplier<Bitmap> bitmapSupplier,
             Rect crop, Intent intent, String tag) {
-        if (bitmapSupplier.get() == null) {
-            Log.e(tag, "No snapshot available, not starting share.");
-            return;
-        }
-
-        UI_HELPER_EXECUTOR.execute(() -> persistBitmapAndStartActivity(context,
-                bitmapSupplier.get(), crop, intent, ImageActionUtils::getShareIntentForImageUri,
-                tag));
+        UI_HELPER_EXECUTOR.execute(() -> {
+            Bitmap bitmap = bitmapSupplier.get();
+            if (bitmap == null) {
+                Log.e(tag, "No snapshot available, not starting share.");
+                return;
+            }
+            persistBitmapAndStartActivity(context, bitmap, crop, intent,
+                    ImageActionUtils::getShareIntentForImageUri, tag);
+        });
     }
 
     /**
      * Launch the activity to share image with shared element transition.
      */
-    @UiThread
     public static void startShareActivity(Context context, Supplier<Bitmap> bitmapSupplier,
             Rect crop, Intent intent, String tag, View sharedElement) {
-        if (bitmapSupplier.get() == null) {
-            Log.e(tag, "No snapshot available, not starting share.");
-            return;
-        }
-
-        UI_HELPER_EXECUTOR.execute(() -> persistBitmapAndStartActivity(context,
-                bitmapSupplier.get(), crop, intent, ImageActionUtils::getShareIntentForImageUri,
-                tag, sharedElement));
+        UI_HELPER_EXECUTOR.execute(() -> {
+            Bitmap bitmap = bitmapSupplier.get();
+            if (bitmap == null) {
+                Log.e(tag, "No snapshot available, not starting share.");
+                return;
+            }
+            persistBitmapAndStartActivity(context, bitmap,
+                    crop, intent, ImageActionUtils::getShareIntentForImageUri, tag, sharedElement);
+        });
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
index b83e26e..e758f5b 100644
--- a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
+++ b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
@@ -17,13 +17,14 @@
 
 import android.content.Context;
 import android.content.res.Resources;
+import android.util.Log;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 
 import com.android.launcher3.Alarm;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
-import com.android.launcher3.testing.TestProtocol;
 
 /**
  * Given positions along x- or y-axis, tracks velocity and acceleration and determines when there is
@@ -31,6 +32,8 @@
  */
 public class MotionPauseDetector {
 
+    private static final String TAG = "MotionPauseDetector";
+
     // The percentage of the previous speed that determines whether this is a rapid deceleration.
     // The bigger this number, the easier it is to trigger the first pause.
     private static final float RAPID_DECELERATION_FACTOR = 0.6f;
@@ -43,6 +46,12 @@
      */
     private static final long HARDER_TRIGGER_TIMEOUT = 400;
 
+    /**
+     * When running in a test harness, if no motion is added for this amount of time, assume the
+     * motion has paused. (We use an increased timeout since sometimes test devices can be slow.)
+     */
+    private static final long TEST_HARNESS_TRIGGER_TIMEOUT = 2000;
+
     private final float mSpeedVerySlow;
     private final float mSpeedSlow;
     private final float mSpeedSomewhatFast;
@@ -85,7 +94,8 @@
         mSpeedSomewhatFast = res.getDimension(R.dimen.motion_pause_detector_speed_somewhat_fast);
         mSpeedFast = res.getDimension(R.dimen.motion_pause_detector_speed_fast);
         mForcePauseTimeout = new Alarm();
-        mForcePauseTimeout.setOnAlarmListener(alarm -> updatePaused(true /* isPaused */));
+        mForcePauseTimeout.setOnAlarmListener(alarm -> updatePaused(true /* isPaused */,
+                "Force pause timeout after " +  alarm.getLastSetTimeout() + "ms" /* reason */));
         mMakePauseHarderToTrigger = makePauseHarderToTrigger;
         mVelocityProvider = new SystemVelocityProvider(axis);
     }
@@ -102,7 +112,7 @@
      */
     public void setDisallowPause(boolean disallowPause) {
         mDisallowPause = disallowPause;
-        updatePaused(mIsPaused);
+        updatePaused(mIsPaused, "Set disallowPause=" + disallowPause);
     }
 
     /**
@@ -119,9 +129,11 @@
      * @param pointerIndex Index for the pointer being tracked in the motion event
      */
     public void addPosition(MotionEvent ev, int pointerIndex) {
-        long timeoutMs = TestProtocol.sForcePauseTimeout != null
-                ? TestProtocol.sForcePauseTimeout
-                : mMakePauseHarderToTrigger ? HARDER_TRIGGER_TIMEOUT : FORCE_PAUSE_TIMEOUT;
+        long timeoutMs = Utilities.IS_RUNNING_IN_TEST_HARNESS
+                ? TEST_HARNESS_TRIGGER_TIMEOUT
+                : mMakePauseHarderToTrigger
+                        ? HARDER_TRIGGER_TIMEOUT
+                        : FORCE_PAUSE_TIMEOUT;
         mForcePauseTimeout.setAlarm(timeoutMs);
         float newVelocity = mVelocityProvider.addMotionEvent(ev, ev.getPointerId(pointerIndex));
         if (mPreviousVelocity != null) {
@@ -134,21 +146,27 @@
         float speed = Math.abs(velocity);
         float previousSpeed = Math.abs(prevVelocity);
         boolean isPaused;
+        String isPausedReason = "";
         if (mIsPaused) {
             // Continue to be paused until moving at a fast speed.
             isPaused = speed < mSpeedFast || previousSpeed < mSpeedFast;
+            isPausedReason = "Was paused, but started moving at a fast speed";
         } else {
             if (velocity < 0 != prevVelocity < 0) {
                 // We're just changing directions, not necessarily stopping.
                 isPaused = false;
+                isPausedReason = "Velocity changed directions";
             } else {
                 isPaused = speed < mSpeedVerySlow && previousSpeed < mSpeedVerySlow;
+                isPausedReason = "Pause requires back to back slow speeds";
                 if (!isPaused && !mHasEverBeenPaused) {
                     // We want to be more aggressive about detecting the first pause to ensure it
                     // feels as responsive as possible; getting two very slow speeds back to back
                     // takes too long, so also check for a rapid deceleration.
                     boolean isRapidDeceleration = speed < previousSpeed * RAPID_DECELERATION_FACTOR;
                     isPaused = isRapidDeceleration && speed < mSpeedSomewhatFast;
+                    isPausedReason = "Didn't have back to back slow speeds, checking for rapid"
+                            + " deceleration on first pause only";
                 }
                 if (mMakePauseHarderToTrigger) {
                     if (speed < mSpeedSlow) {
@@ -156,22 +174,27 @@
                             mSlowStartTime = time;
                         }
                         isPaused = time - mSlowStartTime >= HARDER_TRIGGER_TIMEOUT;
+                        isPausedReason = "Maintained slow speed for sufficient duration when making"
+                                + " pause harder to trigger";
                     } else {
                         mSlowStartTime = 0;
                         isPaused = false;
+                        isPausedReason = "Intentionally making pause harder to trigger";
                     }
                 }
             }
         }
-        updatePaused(isPaused);
+        updatePaused(isPaused, isPausedReason);
     }
 
-    private void updatePaused(boolean isPaused) {
+    private void updatePaused(boolean isPaused, String reason) {
         if (mDisallowPause) {
+            reason = "Disallow pause; otherwise, would have been " + isPaused + " due to " + reason;
             isPaused = false;
         }
         if (mIsPaused != isPaused) {
             mIsPaused = isPaused;
+            Log.d(TAG, "onMotionPauseChanged, paused=" + mIsPaused + " reason=" + reason);
             boolean isFirstDetectedPause = !mHasEverBeenPaused && mIsPaused;
             if (mIsPaused) {
                 AccessibilityManagerCompat.sendPauseDetectedEventToTest(mContext);
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index 6038a22..f2fcd06 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -221,8 +221,7 @@
 
     private boolean updateHandler() {
         mRecentsActivityRotation = inferRecentsActivityRotation(mDisplayRotation);
-        if (mRecentsActivityRotation == mTouchRotation || (isRecentsActivityRotationAllowed()
-                && (mFlags & FLAG_SWIPE_UP_NOT_RUNNING) != 0)) {
+        if (mRecentsActivityRotation == mTouchRotation || isRecentsActivityRotationAllowed()) {
             mOrientationHandler = PagedOrientationHandler.PORTRAIT;
         } else if (mTouchRotation == ROTATION_90) {
             mOrientationHandler = PagedOrientationHandler.LANDSCAPE;
@@ -416,7 +415,7 @@
             fullHeight -= insets.bottom;
         }
 
-        getTaskDimension(mContext, dp, outPivot);
+        getTaskDimension(dp, outPivot);
         float scale = Math.min(outPivot.x / taskView.width(), outPivot.y / taskView.height());
         // We also scale the preview as part of fullScreenParams, so account for that as well.
         if (fullWidth > 0) {
@@ -425,12 +424,6 @@
 
         if (scale == 1) {
             outPivot.set(fullWidth / 2, fullHeight / 2);
-        } else if (dp.isMultiWindowMode) {
-            float denominator = 1 / (scale - 1);
-            // Ensure that the task aligns to right bottom for the root view
-            float y = (scale * taskView.bottom - fullHeight) * denominator;
-            float x = (scale * taskView.right - fullWidth) * denominator;
-            outPivot.set(x, y);
         } else {
             float factor = scale / (scale - 1);
             outPivot.set(taskView.left * factor, taskView.top * factor);
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index 2502359..f1189c9 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -330,4 +330,8 @@
     private boolean isInitialTaskIntentSet() {
         return (mInitialTaskId != INVALID_TASK_ID || mInitialTaskIntent != null);
     }
+
+    public int getInitialTaskId() {
+        return mInitialTaskId;
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index 32e08ff..de527a7 100644
--- a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -41,6 +41,7 @@
 import com.android.launcher3.Hotseat;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.QuickstepTransitionManager;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutAndWidgetContainer;
 import com.android.launcher3.Workspace;
@@ -60,10 +61,10 @@
 public class StaggeredWorkspaceAnim {
 
     private static final int APP_CLOSE_ROW_START_DELAY_MS = 10;
-    // How long it takes to fade in each staggered row.
-    private static final int ALPHA_DURATION_MS = 250;
     // Should be used for animations running alongside this StaggeredWorkspaceAnim.
     public static final int DURATION_MS = 250;
+    public static final int DURATION_TASKBAR_MS =
+            QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION;
 
     private static final float MAX_VELOCITY_PX_PER_S = 22f;
 
@@ -91,16 +92,20 @@
         mSpringTransY = transFactor * launcher.getResources()
                 .getDimensionPixelSize(R.dimen.swipe_up_max_workspace_trans_y);
 
+        DeviceProfile grid = launcher.getDeviceProfile();
+        long duration = grid.isTaskbarPresent ? DURATION_TASKBAR_MS : DURATION_MS;
         if (staggerWorkspace) {
-            DeviceProfile grid = launcher.getDeviceProfile();
             Workspace<?> workspace = launcher.getWorkspace();
             Hotseat hotseat = launcher.getHotseat();
 
-            // Hotseat and QSB takes up two additional rows.
-            int totalRows = grid.inv.numRows + (grid.isVerticalBarLayout() ? 0 : 2);
+            boolean staggerHotseat = !grid.isVerticalBarLayout() && !grid.isTaskbarPresent;
+            boolean staggerQsb =
+                    !grid.isVerticalBarLayout() && !(grid.isTaskbarPresent && grid.isQsbInline);
+            int totalRows = grid.inv.numRows + (staggerHotseat ? 1 : 0) + (staggerQsb ? 1 : 0);
 
             // Add animation for all the visible workspace pages
-            workspace.forEachVisiblePage(page -> addAnimationForPage((CellLayout) page, totalRows));
+            workspace.forEachVisiblePage(
+                    page -> addAnimationForPage((CellLayout) page, totalRows, duration));
 
             boolean workspaceClipChildren = workspace.getClipChildren();
             boolean workspaceClipToPadding = workspace.getClipToPadding();
@@ -119,23 +124,34 @@
                     View child = hotseatIcons.getChildAt(i);
                     CellLayout.LayoutParams lp =
                             ((CellLayout.LayoutParams) child.getLayoutParams());
-                    addStaggeredAnimationForView(child, lp.cellY + 1, totalRows);
+                    addStaggeredAnimationForView(child, lp.cellY + 1, totalRows, duration);
                 }
             } else {
                 final int hotseatRow, qsbRow;
                 if (grid.isTaskbarPresent) {
-                    qsbRow = grid.inv.numRows + 1;
-                    hotseatRow = grid.inv.numRows + 2;
+                    if (grid.isQsbInline) {
+                        qsbRow = grid.inv.numRows + 1;
+                        hotseatRow = grid.inv.numRows + 1;
+                    } else {
+                        qsbRow = grid.inv.numRows + 1;
+                        hotseatRow = grid.inv.numRows + 2;
+                    }
                 } else {
                     hotseatRow = grid.inv.numRows + 1;
                     qsbRow = grid.inv.numRows + 2;
                 }
-                for (int i = hotseatIcons.getChildCount() - 1; i >= 0; i--) {
-                    View child = hotseatIcons.getChildAt(i);
-                    addStaggeredAnimationForView(child, hotseatRow, totalRows);
-                }
 
-                addStaggeredAnimationForView(hotseat.getQsb(), qsbRow, totalRows);
+                // Do not stagger hotseat as a whole when taskbar is present, and stagger QSB only
+                // if it's not inline.
+                if (staggerHotseat) {
+                    for (int i = hotseatIcons.getChildCount() - 1; i >= 0; i--) {
+                        View child = hotseatIcons.getChildAt(i);
+                        addStaggeredAnimationForView(child, hotseatRow, totalRows, duration);
+                    }
+                }
+                if (staggerQsb) {
+                    addStaggeredAnimationForView(hotseat.getQsb(), qsbRow, totalRows, duration);
+                }
             }
 
             mAnimators.addListener(new AnimatorListenerAdapter() {
@@ -153,19 +169,19 @@
         mAnimators.addListener(forEndCallback(launcher::resumeExpensiveViewUpdates));
 
         if (animateOverviewScrim) {
-            PendingAnimation pendingAnimation = new PendingAnimation(DURATION_MS);
+            PendingAnimation pendingAnimation = new PendingAnimation(duration);
             launcher.getWorkspace().getStateTransitionAnimation()
                     .setScrim(pendingAnimation, NORMAL, new StateAnimationConfig());
             mAnimators.play(pendingAnimation.buildAnim());
         }
 
-        addDepthAnimationForState(launcher, NORMAL, DURATION_MS);
+        addDepthAnimationForState(launcher, NORMAL, duration);
 
         mAnimators.play(launcher.getRootView().getSysUiScrim().createSysuiMultiplierAnim(0f, 1f)
-                .setDuration(DURATION_MS));
+                .setDuration(duration));
     }
 
-    private void addAnimationForPage(CellLayout page, int totalRows) {
+    private void addAnimationForPage(CellLayout page, int totalRows, long duration) {
         ShortcutAndWidgetContainer itemsContainer = page.getShortcutsAndWidgets();
 
         boolean pageClipChildren = page.getClipChildren();
@@ -178,7 +194,7 @@
         for (int i = itemsContainer.getChildCount() - 1; i >= 0; i--) {
             View child = itemsContainer.getChildAt(i);
             CellLayout.LayoutParams lp = ((CellLayout.LayoutParams) child.getLayoutParams());
-            addStaggeredAnimationForView(child, lp.cellY + lp.cellVSpan, totalRows);
+            addStaggeredAnimationForView(child, lp.cellY + lp.cellVSpan, totalRows, duration);
         }
 
         mAnimators.addListener(new AnimatorListenerAdapter() {
@@ -231,8 +247,9 @@
      * @param v A view on the workspace.
      * @param row The bottom-most row that contains the view.
      * @param totalRows Total number of rows.
+     * @param duration duration of the animation
      */
-    private void addStaggeredAnimationForView(View v, int row, int totalRows) {
+    private void addStaggeredAnimationForView(View v, int row, int totalRows, long duration) {
         if (mIgnoredView != null && mIgnoredView == v) return;
         // Invert the rows, because we stagger starting from the bottom of the screen.
         int invertedRow = totalRows - row;
@@ -266,7 +283,7 @@
         v.setAlpha(0);
         ObjectAnimator alpha = ObjectAnimator.ofFloat(v, View.ALPHA, 0f, 1f);
         alpha.setInterpolator(LINEAR);
-        alpha.setDuration(ALPHA_DURATION_MS);
+        alpha.setDuration(duration);
         alpha.setStartDelay(startDelay);
         alpha.addListener(new AnimatorListenerAdapter() {
             @Override
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
index b222f51..74e4acc 100644
--- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -253,7 +253,7 @@
                     rotatedPosition.degree, rotatedPosition.positionX, rotatedPosition.positionY);
         } else {
             return mSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mSourceRectHint, mAppBounds,
-                    bounds, insets);
+                    bounds, insets, progress);
         }
     }
 
@@ -279,7 +279,10 @@
         // get the final leash operations but do not apply to the leash.
         final SurfaceControl.Transaction tx =
                 PipSurfaceTransactionHelper.newSurfaceControlTransaction();
-        return onAnimationUpdate(tx, new RectF(mDestinationBounds), END_PROGRESS);
+        final PictureInPictureSurfaceTransaction pipTx =
+                onAnimationUpdate(tx, new RectF(mDestinationBounds), END_PROGRESS);
+        pipTx.setShouldDisableCanAffectSystemUiFlags(true);
+        return pipTx;
     }
 
     private RotatedPosition getRotatedPosition(float progress) {
diff --git a/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java b/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java
index 9bb3d56..5dc4613 100644
--- a/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java
+++ b/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java
@@ -15,12 +15,20 @@
  */
 package com.android.quickstep.util;
 
-import android.content.Context;
-import android.hardware.display.DisplayManager;
-import android.view.Display;
+import static android.view.Display.DEFAULT_DISPLAY;
 
+import android.content.Context;
+import android.util.ArrayMap;
+import android.view.Surface;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
+
+import com.android.launcher3.util.WindowBounds;
+import com.android.launcher3.util.window.CachedDisplayInfo;
 import com.android.launcher3.util.window.WindowManagerProxy;
 
+import java.util.Set;
+
 /**
  * Extension of {@link WindowManagerProxy} with some assumption for the default system Launcher
  */
@@ -31,23 +39,23 @@
     }
 
     @Override
-    protected String getDisplayId(Display display) {
-        return display.getUniqueId();
+    public int getRotation(Context displayInfoContext) {
+        return displayInfoContext.getResources().getConfiguration().windowConfiguration
+                .getRotation();
     }
 
     @Override
-    public boolean isInternalDisplay(Display display) {
-        return display.getType() == Display.TYPE_INTERNAL;
-    }
-
-    @Override
-    public int getRotation(Context context) {
-        return context.getResources().getConfiguration().windowConfiguration.getRotation();
-    }
-
-    @Override
-    protected Display[] getDisplays(Context context) {
-        return context.getSystemService(DisplayManager.class).getDisplays(
-                DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED);
+    public ArrayMap<CachedDisplayInfo, WindowBounds[]> estimateInternalDisplayBounds(
+            Context displayInfoContext) {
+        ArrayMap<CachedDisplayInfo, WindowBounds[]> result = new ArrayMap<>();
+        WindowManager windowManager = displayInfoContext.getSystemService(WindowManager.class);
+        Set<WindowMetrics> possibleMaximumWindowMetrics =
+                windowManager.getPossibleMaximumWindowMetrics(DEFAULT_DISPLAY);
+        for (WindowMetrics windowMetrics : possibleMaximumWindowMetrics) {
+            CachedDisplayInfo info = getDisplayInfo(windowMetrics, Surface.ROTATION_0);
+            WindowBounds[] bounds = estimateWindowBounds(displayInfoContext, info);
+            result.put(info, bounds);
+        }
+        return result;
     }
 }
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 5212755..8c48443 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -40,7 +40,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
 import com.android.launcher3.util.TraceHelper;
 import com.android.quickstep.AnimatedFloat;
 import com.android.quickstep.BaseActivityInterface;
@@ -100,7 +100,7 @@
     // Cached calculations
     private boolean mLayoutValid = false;
     private int mOrientationStateId;
-    private StagedSplitBounds mStagedSplitBounds;
+    private SplitBounds mSplitBounds;
     private boolean mDrawsBelowRecents;
     private boolean mIsGridTask;
     private int mTaskRectTranslationX;
@@ -152,13 +152,13 @@
         }
 
         Rect fullTaskSize;
-        if (mStagedSplitBounds != null) {
+        if (mSplitBounds != null) {
             // The task rect changes according to the staged split task sizes, but recents
             // fullscreen scale and pivot remains the same since the task fits into the existing
             // sized task space bounds
             fullTaskSize = new Rect(mTaskRect);
             mOrientationState.getOrientationHandler()
-                    .setSplitTaskSwipeRect(mDp, mTaskRect, mStagedSplitBounds, mStagePosition);
+                    .setSplitTaskSwipeRect(mDp, mTaskRect, mSplitBounds, mStagePosition);
             mTaskRect.offset(mTaskRectTranslationX, mTaskRectTranslationY);
         } else {
             fullTaskSize = mTaskRect;
@@ -180,10 +180,10 @@
      *
      * @param splitInfo set to {@code null} when not in staged split mode
      */
-    public void setPreview(RemoteAnimationTargetCompat runningTarget, StagedSplitBounds splitInfo) {
+    public void setPreview(RemoteAnimationTargetCompat runningTarget, SplitBounds splitInfo) {
         setPreview(runningTarget);
-        mStagedSplitBounds = splitInfo;
-        if (mStagedSplitBounds == null) {
+        mSplitBounds = splitInfo;
+        if (mSplitBounds == null) {
             mStagePosition = STAGE_POSITION_UNDEFINED;
             return;
         }
@@ -390,10 +390,8 @@
                 .withWindowCrop(mTmpCropRect)
                 .withCornerRadius(getCurrentCornerRadius());
 
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && params.getRecentsSurface() != null) {
-            // When relativeLayer = 0, it reverts the surfaces back to the original order.
-            builder.withRelativeLayerTo(params.getRecentsSurface(),
-                    mDrawsBelowRecents ? Integer.MIN_VALUE : 0);
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            builder.withLayer(mDrawsBelowRecents ? Integer.MIN_VALUE + 1 : Integer.MAX_VALUE);
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/util/TaskVisualsChangeListener.java b/quickstep/src/com/android/quickstep/util/TaskVisualsChangeListener.java
new file mode 100644
index 0000000..66bff73
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/TaskVisualsChangeListener.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util;
+
+import android.os.UserHandle;
+
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+
+/**
+ * Listener for receiving various task properties changes
+ */
+public interface TaskVisualsChangeListener {
+
+    /**
+     * Called when the task thumbnail changes
+     */
+    default Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData) {
+        return null;
+    }
+
+    /**
+     * Called when the icon for a task changes
+     */
+    default void onTaskIconChanged(String pkg, UserHandle user) {}
+
+    /**
+     * Called when the icon for a task changes
+     */
+    default void onTaskIconChanged(int taskId) {}
+}
diff --git a/quickstep/src/com/android/quickstep/util/TransformParams.java b/quickstep/src/com/android/quickstep/util/TransformParams.java
index 75d6001..a7f25d4 100644
--- a/quickstep/src/com/android/quickstep/util/TransformParams.java
+++ b/quickstep/src/com/android/quickstep/util/TransformParams.java
@@ -139,10 +139,12 @@
 
     public SurfaceParams[] createSurfaceParams(BuilderProxy proxy) {
         RemoteAnimationTargets targets = mTargetSet;
-        SurfaceParams[] surfaceParams = new SurfaceParams[targets.unfilteredApps.length];
+        final int appLength =  targets.unfilteredApps.length;
+        final int wallpaperLength = targets.wallpapers != null ? targets.wallpapers.length : 0;
+        SurfaceParams[] surfaceParams = new SurfaceParams[appLength + wallpaperLength];
         mRecentsSurface = getRecentsSurface(targets);
 
-        for (int i = 0; i < targets.unfilteredApps.length; i++) {
+        for (int i = 0; i < appLength; i++) {
             RemoteAnimationTargetCompat app = targets.unfilteredApps[i];
             SurfaceParams.Builder builder = new SurfaceParams.Builder(app.leash);
 
@@ -166,6 +168,12 @@
             }
             surfaceParams[i] = builder.build();
         }
+        // always put wallpaper layer to bottom.
+        for (int i = 0; i < wallpaperLength; i++) {
+            RemoteAnimationTargetCompat wallpaper = targets.wallpapers[i];
+            surfaceParams[appLength + i] = new SurfaceParams.Builder(wallpaper.leash)
+                    .withLayer(Integer.MIN_VALUE).build();
+        }
         return surfaceParams;
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
index 79b15c7..76552a3 100644
--- a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
+++ b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
@@ -53,7 +53,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
 import com.android.systemui.shared.recents.model.Task;
 
 import java.lang.annotation.Retention;
@@ -103,7 +103,7 @@
      */
     private float mModalOffset = 0f;
     @Nullable
-    private StagedSplitBounds mStagedSplitBounds;
+    private SplitBounds mSplitBounds;
     private int mSplitBannerConfig = SPLIT_BANNER_FULLSCREEN;
     private float mSplitOffsetTranslationY;
     private float mSplitOffsetTranslationX;
@@ -164,9 +164,9 @@
         });
     }
 
-    public void setSplitConfiguration(StagedSplitBounds stagedSplitBounds) {
-        mStagedSplitBounds = stagedSplitBounds;
-        if (mStagedSplitBounds == null
+    public void setSplitConfiguration(SplitBounds splitBounds) {
+        mSplitBounds = splitBounds;
+        if (mSplitBounds == null
                 || !mActivity.getDeviceProfile().isTablet
                 || mTaskView.isFocusedTask()) {
             mSplitBannerConfig = SPLIT_BANNER_FULLSCREEN;
@@ -180,11 +180,11 @@
         }
 
         // For landscape grid, for 30% width we only show icon, otherwise show icon and time
-        if (mTask.key.id == mStagedSplitBounds.leftTopTaskId) {
-            mSplitBannerConfig = mStagedSplitBounds.leftTaskPercent < THRESHOLD_LEFT_ICON_ONLY ?
+        if (mTask.key.id == mSplitBounds.leftTopTaskId) {
+            mSplitBannerConfig = mSplitBounds.leftTaskPercent < THRESHOLD_LEFT_ICON_ONLY ?
                     SPLIT_GRID_BANNER_SMALL : SPLIT_GRID_BANNER_LARGE;
         } else {
-            mSplitBannerConfig = mStagedSplitBounds.leftTaskPercent > THRESHOLD_RIGHT_ICON_ONLY ?
+            mSplitBannerConfig = mSplitBounds.leftTaskPercent > THRESHOLD_RIGHT_ICON_ONLY ?
                     SPLIT_GRID_BANNER_SMALL : SPLIT_GRID_BANNER_LARGE;
         }
     }
@@ -321,7 +321,7 @@
         PagedOrientationHandler orientationHandler = mTaskView.getPagedOrientationHandler();
         Pair<Float, Float> translations = orientationHandler
                 .getDwbLayoutTranslations(mTaskView.getMeasuredWidth(),
-                        mTaskView.getMeasuredHeight(), mStagedSplitBounds, deviceProfile,
+                        mTaskView.getMeasuredHeight(), mSplitBounds, deviceProfile,
                         mTaskView.getThumbnails(), mTask.key.id, mBanner);
         mSplitOffsetTranslationX = translations.first;
         mSplitOffsetTranslationY = translations.second;
diff --git a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
index c980d1e..835c9f7 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
@@ -1,5 +1,6 @@
 package com.android.quickstep.views;
 
+import static com.android.launcher3.AbstractFloatingView.TYPE_TASK_MENU;
 import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
@@ -19,6 +20,7 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.InsettableFrameLayout;
 import com.android.launcher3.LauncherAnimUtils;
@@ -110,13 +112,19 @@
      */
     public static FloatingTaskView getFloatingTaskView(StatefulActivity launcher,
             View originalView, @Nullable Bitmap thumbnail, Drawable icon, RectF positionOut) {
-        final BaseDragLayer dragLayer = launcher.getDragLayer();
-        ViewGroup parent = (ViewGroup) dragLayer.getParent();
+        final ViewGroup dragLayer = launcher.getDragLayer();
         final FloatingTaskView floatingView = (FloatingTaskView) launcher.getLayoutInflater()
-                .inflate(R.layout.floating_split_select_view, parent, false);
+                .inflate(R.layout.floating_split_select_view, dragLayer, false);
 
         floatingView.init(launcher, originalView, thumbnail, icon, positionOut);
-        parent.addView(floatingView);
+        // Add this animating view underneath the existing open task menu view (if there is one)
+        View openTaskView = AbstractFloatingView.getOpenView(launcher, TYPE_TASK_MENU);
+        int openTaskViewIndex = dragLayer.indexOfChild(openTaskView);
+        if (openTaskViewIndex == -1) {
+            // Add to top if not
+            openTaskViewIndex = dragLayer.getChildCount();
+        }
+        dragLayer.addView(floatingView, openTaskViewIndex - 1);
         return floatingView;
     }
 
@@ -125,7 +133,7 @@
         Utilities.getBoundsForViewInDragLayer(mActivity.getDragLayer(), originalView, viewBounds,
                 false /* ignoreTransform */, null /* recycle */,
                 mStartingPosition);
-        final InsettableFrameLayout.LayoutParams lp = new InsettableFrameLayout.LayoutParams(
+        final BaseDragLayer.LayoutParams lp = new BaseDragLayer.LayoutParams(
                 Math.round(mStartingPosition.width()),
                 Math.round(mStartingPosition.height()));
         initPosition(mStartingPosition, lp);
@@ -246,7 +254,7 @@
      *                        offscreen).
      */
     void centerIconView(IconView iconView, float onScreenRectCenterX, float onScreenRectCenterY) {
-        mOrientationHandler.updateStagedSplitIconParams(iconView, onScreenRectCenterX,
+        mOrientationHandler.updateSplitIconParams(iconView, onScreenRectCenterX,
                 onScreenRectCenterY, mFullscreenParams.mScaleX, mFullscreenParams.mScaleY,
                 iconView.getDrawableWidth(), iconView.getDrawableHeight(),
                 mActivity.getDeviceProfile(), mStagePosition);
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index 244a794..cb88068 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -17,7 +17,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.RunnableList;
-import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
 import com.android.launcher3.util.TransformingTouchDelegate;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.TaskIconCache;
@@ -53,7 +53,7 @@
     private CancellableTask mIconLoadRequest2;
     private final float[] mIcon2CenterCoords = new float[2];
     private TransformingTouchDelegate mIcon2TouchDelegate;
-    @Nullable private StagedSplitBounds mSplitBoundsConfig;
+    @Nullable private SplitBounds mSplitBoundsConfig;
     private final DigitalWellBeingToast mDigitalWellBeingToast2;
 
     public GroupedTaskView(Context context) {
@@ -78,7 +78,7 @@
     }
 
     public void bind(Task primary, Task secondary, RecentsOrientedState orientedState,
-            @Nullable StagedSplitBounds splitBoundsConfig) {
+            @Nullable SplitBounds splitBoundsConfig) {
         super.bind(primary, orientedState);
         mSecondaryTask = secondary;
         mTaskIdContainer[1] = secondary.key.id;
@@ -126,8 +126,8 @@
         }
     }
 
-    public void updateSplitBoundsConfig(StagedSplitBounds stagedSplitBounds) {
-        mSplitBoundsConfig = stagedSplitBounds;
+    public void updateSplitBoundsConfig(SplitBounds splitBounds) {
+        mSplitBoundsConfig = splitBounds;
         invalidate();
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 306ebd7..a736583 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -39,6 +39,7 @@
 import com.android.launcher3.popup.QuickstepSystemShortcut;
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statemanager.StateManager.StateListener;
+import com.android.launcher3.util.PendingSplitSelectInfo;
 import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.quickstep.LauncherActivityInterface;
 import com.android.quickstep.util.SplitSelectStateController;
@@ -89,6 +90,21 @@
     }
 
     @Override
+    public void onTaskIconChanged(int taskId) {
+        // If Launcher needs to return to split select state, do it now, after the icon has updated.
+        if (mActivity.hasPendingSplitSelectInfo()) {
+            PendingSplitSelectInfo recoveryData = mActivity.getPendingSplitSelectInfo();
+            if (recoveryData.getStagedTaskId() == taskId) {
+                initiateSplitSelect(
+                        getTaskViewByTaskId(recoveryData.getStagedTaskId()),
+                        recoveryData.getStagePosition()
+                );
+                mActivity.finishSplitSelectRecovery();
+            }
+        }
+    }
+
+    @Override
     public void reset() {
         super.reset();
 
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index 1c4e497..bfb43c1 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -22,10 +22,8 @@
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.View.OnClickListener;
-import android.view.ViewGroup;
 import android.widget.Button;
 import android.widget.FrameLayout;
-import android.widget.LinearLayout;
 
 import androidx.annotation.IntDef;
 import androidx.annotation.Nullable;
@@ -80,8 +78,9 @@
     private static final int INDEX_VISIBILITY_ALPHA = 1;
     private static final int INDEX_FULLSCREEN_ALPHA = 2;
     private static final int INDEX_HIDDEN_FLAGS_ALPHA = 3;
+    private static final int INDEX_SHARE_TARGET_ALPHA = 4;
 
-    private final MultiValueAlpha mMultiValueAlpha;
+    private MultiValueAlpha mMultiValueAlpha;
     private Button mSplitButton;
 
     @ActionsHiddenFlags
@@ -107,13 +106,14 @@
 
     public OverviewActionsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr, 0);
-        mMultiValueAlpha = new MultiValueAlpha(this, 5);
-        mMultiValueAlpha.setUpdateVisibility(true);
     }
 
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
+        mMultiValueAlpha = new MultiValueAlpha(findViewById(R.id.action_buttons), 5);
+        mMultiValueAlpha.setUpdateVisibility(true);
+
         findViewById(R.id.action_screenshot).setOnClickListener(this);
 
         mSplitButton = findViewById(R.id.action_split);
@@ -195,6 +195,10 @@
         return mMultiValueAlpha.getProperty(INDEX_FULLSCREEN_ALPHA);
     }
 
+    public AlphaProperty getShareTargetAlpha() {
+        return mMultiValueAlpha.getProperty(INDEX_SHARE_TARGET_ALPHA);
+    }
+
     /**
      * Offsets OverviewActionsView horizontal position based on 3 button nav container in taskbar.
      */
@@ -233,10 +237,6 @@
             return 0;
         }
 
-        if (mDp.isVerticalBarLayout()) {
-            return mDp.getInsets().bottom;
-        }
-
         if (!mDp.isGestureMode && mDp.isTaskbarPresent) {
             return mDp.getOverviewActionsClaimedSpaceBelow();
         }
@@ -254,12 +254,6 @@
         mTaskSize.set(taskSize);
         updateVerticalMargin(DisplayController.getNavigationMode(getContext()));
 
-        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
-                dp.isVerticalBarLayout() ? 0 : dp.overviewActionsButtonSpacing,
-                ViewGroup.LayoutParams.MATCH_PARENT);
-        params.weight = dp.isVerticalBarLayout() ? 1 : 0;
-        findViewById(R.id.action_split_space).setLayoutParams(params);
-
         requestLayout();
 
         mSplitButton.setCompoundDrawablesWithIntrinsicBounds(
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 2360396..274a691 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -34,6 +34,7 @@
 import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.launcher3.anim.Interpolators.ACCEL_0_75;
 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
@@ -140,8 +141,8 @@
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.ResourceBasedOverride.Overrides;
 import com.android.launcher3.util.RunnableList;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
 import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
-import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.TranslateEdgeEffect;
 import com.android.launcher3.util.ViewPool;
@@ -151,7 +152,6 @@
 import com.android.quickstep.RecentsAnimationController;
 import com.android.quickstep.RecentsAnimationTargets;
 import com.android.quickstep.RecentsModel;
-import com.android.quickstep.RecentsModel.TaskVisualsChangeListener;
 import com.android.quickstep.RemoteAnimationTargets;
 import com.android.quickstep.RemoteTargetGluer;
 import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
@@ -169,6 +169,7 @@
 import com.android.quickstep.util.SplitSelectStateController;
 import com.android.quickstep.util.SurfaceTransactionApplier;
 import com.android.quickstep.util.TaskViewSimulator;
+import com.android.quickstep.util.TaskVisualsChangeListener;
 import com.android.quickstep.util.TransformParams;
 import com.android.quickstep.util.VibratorWrapper;
 import com.android.systemui.plugins.ResourceProvider;
@@ -624,12 +625,14 @@
     @Nullable
     private View mSecondSplitHiddenView;
     @Nullable
-    private StagedSplitBounds mSplitBoundsConfig;
+    private SplitBounds mSplitBoundsConfig;
     private final Toast mSplitToast = Toast.makeText(getContext(),
             R.string.toast_split_select_app, Toast.LENGTH_SHORT);
     private final Toast mSplitUnsupportedToast = Toast.makeText(getContext(),
             R.string.toast_split_app_unsupported, Toast.LENGTH_SHORT);
 
+    private SplitInstructionsView mSplitInstructionsView;
+
     @Nullable
     private QuickstepSystemShortcut.SplitSelectSource mSplitSelectSource;
 
@@ -1445,11 +1448,11 @@
 
             if (hasMultipleTasks) {
                 boolean firstTaskIsLeftTopTask =
-                        groupTask.mStagedSplitBounds.leftTopTaskId == groupTask.task1.key.id;
+                        groupTask.mSplitBounds.leftTopTaskId == groupTask.task1.key.id;
                 Task leftTopTask = firstTaskIsLeftTopTask ? groupTask.task1 : groupTask.task2;
                 Task rightBottomTask = firstTaskIsLeftTopTask ? groupTask.task2 : groupTask.task1;
                 ((GroupedTaskView) taskView).bind(leftTopTask, rightBottomTask, mOrientationState,
-                        groupTask.mStagedSplitBounds);
+                        groupTask.mSplitBounds);
             } else {
                 taskView.bind(groupTask.task1, mOrientationState);
             }
@@ -2300,7 +2303,7 @@
         boolean runningTaskTileHidden = mRunningTaskTileHidden;
         setCurrentTask(runningTaskViewId);
         mFocusedTaskViewId = runningTaskViewId;
-        setCurrentPage(getRunningTaskIndex());
+        runOnPageScrollsInitialized(() -> setCurrentPage(getRunningTaskIndex()));
         setRunningTaskViewShowScreenshot(false);
         setRunningTaskHidden(runningTaskTileHidden);
         // Update task size after setting current task.
@@ -2764,11 +2767,15 @@
             mFirstFloatingTaskView.addAnimation(anim, startingTaskRect, mTempRect,
                     false /* fadeWithThumbnail */, true /* isStagedTask */);
         }
+
+        mSplitInstructionsView = SplitInstructionsView.getSplitInstructionsView(mActivity);
+        mSplitInstructionsView.setAlpha(0);
+        anim.addFloat(mSplitInstructionsView, SplitInstructionsView.ALPHA_FLOAT, 0, 1, ACCEL);
+
         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 {
@@ -4007,7 +4014,8 @@
                 stagePosition);
         mSplitHiddenTaskViewIndex = indexOfChild(taskView);
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            finishRecentsAnimation(true, null);
+            finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
+                    null /* onFinishComplete */);
         }
     }
 
@@ -4091,6 +4099,10 @@
         mSecondSplitHiddenView.setVisibility(INVISIBLE);
         InteractionJankMonitorWrapper.begin(this,
                 InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER, "Second tile selected");
+
+        // Fade out all other views underneath placeholders
+        ObjectAnimator tvFade = ObjectAnimator.ofFloat(this, RecentsView.CONTENT_ALPHA,1, 0);
+        pendingAnimation.add(tvFade, DEACCEL_2, SpringProperty.DEFAULT);
         pendingAnimation.buildAnim().start();
         return true;
     }
@@ -4099,12 +4111,16 @@
     @SuppressLint("WrongCall")
     protected void resetFromSplitSelectionState() {
         if (mSplitSelectSource != null || mSplitHiddenTaskViewIndex != -1) {
+            if (mSplitInstructionsView != null) {
+                mActivity.getDragLayer().removeView(mSplitInstructionsView);
+                mSplitInstructionsView = null;
+            }
             if (mFirstFloatingTaskView != null) {
-                mActivity.getRootView().removeView(mFirstFloatingTaskView);
+                mActivity.getDragLayer().removeView(mFirstFloatingTaskView);
                 mFirstFloatingTaskView = null;
             }
             if (mSecondFloatingTaskView != null) {
-                mActivity.getRootView().removeView(mSecondFloatingTaskView);
+                mActivity.getDragLayer().removeView(mSecondFloatingTaskView);
                 mSecondFloatingTaskView = null;
                 mSecondSplitHiddenView.setVisibility(VISIBLE);
                 mSecondSplitHiddenView = null;
@@ -4164,6 +4180,10 @@
         taskViewsFloat.first.set(this, getSplitSelectTranslation());
         taskViewsFloat.second.set(this, 0f);
 
+        if (mSplitInstructionsView != null) {
+            mSplitInstructionsView.ensureProperRotation();
+        }
+
         applySplitPrimaryScrollOffset();
     }
 
@@ -4473,7 +4493,7 @@
         RemoteTargetGluer gluer = new RemoteTargetGluer(getContext(), getSizeStrategy());
         mRemoteTargetHandles = gluer.assignTargetsForSplitScreen(
                 getContext(), recentsAnimationTargets);
-        mSplitBoundsConfig = gluer.getStagedSplitBounds();
+        mSplitBoundsConfig = gluer.getSplitBounds();
         // Add release check to the targets from the RemoteTargetGluer and not the targets
         // passed in because in the event we're in split screen, we use the passed in targets
         // to create new RemoteAnimationTargets in assignTargetsForSplitScreen(), and the
@@ -4523,12 +4543,6 @@
             @Nullable Runnable onFinishComplete) {
         // TODO(b/197232424#comment#10) Move this back into onRecentsAnimationComplete(). Maybe?
         cleanupRemoteTargets();
-        if (!toRecents && ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            // 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.INSTANCE.get(getContext()).setSplitScreenMinimized(false);
-        }
 
         if (mRecentsAnimationController == null) {
             if (onFinishComplete != null) {
@@ -4549,6 +4563,7 @@
                     new PictureInPictureSurfaceTransaction.Builder()
                             .setAlpha(0f)
                             .build();
+            tx.setShouldDisableCanAffectSystemUiFlags(false);
             int[] taskIds = TopTaskTracker.INSTANCE.get(getContext()).getRunningSplitTaskIds();
             for (int taskId : taskIds) {
                 mRecentsAnimationController.setFinishTaskTransaction(taskId,
@@ -4859,10 +4874,10 @@
     }
 
     private void updateEnabledOverlays() {
-        int overlayEnabledPage = mOverlayEnabled ? getNextPage() : -1;
         int taskCount = getTaskViewCount();
         for (int i = 0; i < taskCount; i++) {
-            requireTaskViewAt(i).setOverlayEnabled(i == overlayEnabledPage);
+            TaskView taskView = requireTaskViewAt(i);
+            taskView.setOverlayEnabled(mOverlayEnabled && isTaskViewFullyVisible(taskView));
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
new file mode 100644
index 0000000..7d94505
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 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.quickstep.views;
+
+import static com.android.launcher3.util.DisplayController.NavigationMode.THREE_BUTTONS;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.FloatProperty;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.R;
+import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.util.DisplayController;
+
+/**
+ * A rounded rectangular component containing a single TextView.
+ * Appears when a split is in progress, and tells the user to select a second app to initiate
+ * splitscreen.
+ *
+ * Appears and disappears concurrently with a FloatingTaskView.
+ */
+public class SplitInstructionsView extends FrameLayout {
+    private final StatefulActivity mLauncher;
+
+    public static final FloatProperty<SplitInstructionsView> ALPHA_FLOAT =
+            new FloatProperty<SplitInstructionsView>("SplitInstructionsAlpha") {
+                @Override
+                public void setValue(SplitInstructionsView splitInstructionsView, float v) {
+                    splitInstructionsView.setVisibility(v != 0 ? VISIBLE : GONE);
+                    splitInstructionsView.setAlpha(v);
+                }
+
+                @Override
+                public Float get(SplitInstructionsView splitInstructionsView) {
+                    return splitInstructionsView.getAlpha();
+                }
+            };
+
+    public SplitInstructionsView(Context context) {
+        this(context, null);
+    }
+
+    public SplitInstructionsView(Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public SplitInstructionsView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mLauncher = (StatefulActivity) context;
+    }
+
+    static SplitInstructionsView getSplitInstructionsView(StatefulActivity launcher) {
+        ViewGroup dragLayer = launcher.getDragLayer();
+        final SplitInstructionsView splitInstructionsView =
+                (SplitInstructionsView) launcher.getLayoutInflater().inflate(
+                        R.layout.split_instructions_view,
+                        dragLayer,
+                        false
+                );
+
+        dragLayer.addView(splitInstructionsView);
+        return splitInstructionsView;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        ensureProperRotation();
+    }
+
+    void ensureProperRotation() {
+        ((RecentsView) mLauncher.getOverviewPanel()).getPagedOrientationHandler()
+                .setSplitInstructionsParams(
+                        this,
+                        mLauncher.getDeviceProfile(),
+                        getMeasuredHeight(),
+                        getMeasuredWidth(),
+                        getThreeButtonNavShift()
+                );
+    }
+
+    // In some cases, when user is using 3-button nav, there isn't enough room for both the
+    // 3-button nav and a centered SplitInstructionsView. This function will return an int that will
+    // be used to shift the SplitInstructionsView over a bit so that everything looks well-spaced.
+    // In many cases, this will return 0, since we don't need to shift it away from the center.
+    int getThreeButtonNavShift() {
+        DeviceProfile dp = mLauncher.getDeviceProfile();
+        if ((DisplayController.getNavigationMode(getContext()) == THREE_BUTTONS)
+                && ((dp.isTwoPanels) || (dp.isTablet && !dp.isLandscape))) {
+            int navButtonWidth = getResources().getDimensionPixelSize(
+                    R.dimen.taskbar_nav_buttons_size);
+            int extraMargin = getResources().getDimensionPixelSize(
+                    R.dimen.taskbar_contextual_button_margin);
+            // Explanation: The 3-button nav for non-phones sits on one side of the screen, taking
+            // up 3 buttons + a side margin worth of space. Our splitInstructionsView starts in the
+            // center of the screen and we want to center it in the remaining space, therefore we
+            // want to shift it over by half the 3-button layout's width.
+            // If the user is using an RtL layout, we shift it the opposite way.
+            return -((3 * navButtonWidth + extraMargin) / 2) * (isLayoutRtl() ? -1 : 1);
+        } else {
+            return 0;
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index 3803f1b..c1711d1 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -16,7 +16,6 @@
 
 package com.android.quickstep.views;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
 import static com.android.quickstep.views.TaskThumbnailView.DIM_ALPHA;
@@ -232,8 +231,7 @@
     private void addMenuOptions(TaskIdAttributeContainer taskContainer) {
         mTaskName.setText(TaskUtils.getTitle(getContext(), taskContainer.getTask()));
         mTaskName.setOnClickListener(v -> close(true));
-        TaskOverlayFactory.getEnabledShortcuts(mTaskView, mActivity.getDeviceProfile(),
-                taskContainer)
+        TaskOverlayFactory.getEnabledShortcuts(mTaskView, taskContainer)
                 .forEach(this::addMenuOption);
     }
 
@@ -245,17 +243,9 @@
         LayoutParams lp = (LayoutParams) menuOptionView.getLayoutParams();
         mTaskView.getPagedOrientationHandler().setLayoutParamsForTaskMenuOptionItem(lp,
                 menuOptionView, mActivity.getDeviceProfile());
-        menuOptionView.setOnClickListener(view -> {
-            if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-                RecentsView recentsView = mTaskView.getRecentsView();
-                recentsView.switchToScreenshot(null,
-                        () -> recentsView.finishRecentsAnimation(true /* toRecents */,
-                                false /* shouldPip */,
-                                () -> menuOption.onClick(view)));
-            } else {
-                menuOption.onClick(view);
-            }
-        });
+        // Set an onClick listener on each menu option. The onClick method is responsible for
+        // ending LiveTile mode on the thumbnail if needed.
+        menuOptionView.setOnClickListener(menuOption::onClick);
         mOptionLayout.addView(menuOptionView);
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
index 06a5793..b586ac3 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
@@ -164,7 +164,7 @@
     private fun addMenuOptions() {
         // Add the options
         TaskOverlayFactory
-            .getEnabledShortcuts(taskView, mActivityContext.deviceProfile, taskContainer)
+            .getEnabledShortcuts(taskView, taskContainer)
             .forEach { this.addMenuOption(it) }
 
         // Add the spaces between items
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index d8120ff..32dc4d8 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -52,6 +52,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.SystemUiController;
+import com.android.launcher3.util.SystemUiController.SystemUiControllerFlags;
 import com.android.quickstep.TaskOverlayFactory.TaskOverlay;
 import com.android.quickstep.views.TaskView.FullscreenDrawParams;
 import com.android.systemui.shared.recents.model.Task;
@@ -247,6 +248,7 @@
     }
 
 
+    @SystemUiControllerFlags
     public int getSysUiStatusNavFlags() {
         if (mThumbnailData != null) {
             int flags = 0;
@@ -451,9 +453,8 @@
 
             // Landscape vs portrait change.
             // Note: Disable rotation in grid layout.
-            boolean windowingModeSupportsRotation = !dp.isMultiWindowMode
-                    && thumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN
-                    && !dp.isTablet;
+            boolean windowingModeSupportsRotation =
+                    thumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN && !dp.isTablet;
             isOrientationDifferent = isOrientationChange(deltaRotate)
                     && windowingModeSupportsRotation;
             if (canvasWidth == 0 || canvasHeight == 0 || scale == 0) {
@@ -559,44 +560,16 @@
                 thumbnailScale = targetW / (croppedWidth * scale);
             }
 
-            Rect splitScreenInsets = dp.getInsets();
             if (!isRotated) {
-                // No Rotation
-                if (dp.isMultiWindowMode) {
-                    mClippedInsets.offsetTo(splitScreenInsets.left * scale,
-                            splitScreenInsets.top * scale);
-                } else {
-                    mClippedInsets.offsetTo(thumbnailClipHint.left * scale,
-                            thumbnailClipHint.top * scale);
-                }
                 mMatrix.setTranslate(
                         -thumbnailClipHint.left * scale,
                         -thumbnailClipHint.top * scale);
             } else {
-                setThumbnailRotation(deltaRotate, thumbnailClipHint, scale, thumbnailBounds, dp);
+                setThumbnailRotation(deltaRotate, thumbnailBounds);
             }
 
-            final float widthWithInsets;
-            final float heightWithInsets;
-            if (isOrientationDifferent) {
-                widthWithInsets = thumbnailBounds.height() * thumbnailScale;
-                heightWithInsets = thumbnailBounds.width() * thumbnailScale;
-            } else {
-                widthWithInsets = thumbnailBounds.width() * thumbnailScale;
-                heightWithInsets = thumbnailBounds.height() * thumbnailScale;
-            }
-            mClippedInsets.left *= thumbnailScale;
-            mClippedInsets.top *= thumbnailScale;
-
-            if (dp.isMultiWindowMode) {
-                mClippedInsets.right = splitScreenInsets.right * scale * thumbnailScale;
-                mClippedInsets.bottom = splitScreenInsets.bottom * scale * thumbnailScale;
-            } else {
-                mClippedInsets.right = Math.max(0,
-                        widthWithInsets - mClippedInsets.left - canvasWidth);
-                mClippedInsets.bottom = Math.max(0,
-                        heightWithInsets - mClippedInsets.top - canvasHeight);
-            }
+            float canvasScreenRatio = canvasWidth / (float) dp.widthPx;
+            mClippedInsets.set(0, 0, 0, dp.taskbarSize * canvasScreenRatio);
 
             mMatrix.postScale(thumbnailScale, thumbnailScale);
             mIsOrientationChanged = isOrientationDifferent;
@@ -617,44 +590,32 @@
             return deltaRotation == Surface.ROTATION_90 || deltaRotation == Surface.ROTATION_270;
         }
 
-        private void setThumbnailRotation(int deltaRotate, RectF thumbnailInsets, float scale,
-                Rect thumbnailPosition, DeviceProfile dp) {
-            float newLeftInset = 0;
-            float newTopInset = 0;
+        private void setThumbnailRotation(int deltaRotate, Rect thumbnailPosition) {
             float translateX = 0;
             float translateY = 0;
 
             mMatrix.setRotate(90 * deltaRotate);
             switch (deltaRotate) { /* Counter-clockwise */
                 case Surface.ROTATION_90:
-                    newLeftInset = thumbnailInsets.bottom;
-                    newTopInset = thumbnailInsets.left;
                     translateX = thumbnailPosition.height();
                     break;
                 case Surface.ROTATION_270:
-                    newLeftInset = thumbnailInsets.top;
-                    newTopInset = thumbnailInsets.right;
                     translateY = thumbnailPosition.width();
                     break;
                 case Surface.ROTATION_180:
-                    newLeftInset = -thumbnailInsets.top;
-                    newTopInset = -thumbnailInsets.left;
                     translateX = thumbnailPosition.width();
                     translateY = thumbnailPosition.height();
                     break;
             }
-            mClippedInsets.offsetTo(newLeftInset * scale, newTopInset * scale);
             mMatrix.postTranslate(translateX, translateY);
-            if (TaskView.useFullThumbnail(dp)) {
-                mMatrix.postTranslate(-mClippedInsets.left, -mClippedInsets.top);
-            }
         }
 
         /**
          * Insets to used for clipping the thumbnail (in case it is drawing outside its own space)
          */
         public RectF getInsetsToDrawInFullscreen(DeviceProfile dp) {
-            return TaskView.useFullThumbnail(dp) ? mClippedInsets : EMPTY_RECT_F;
+            return dp.isTaskbarPresent && !dp.isTaskbarPresentInApps
+                    ? mClippedInsets : EMPTY_RECT_F;
         }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index d58bb7c..68e9f5a 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -19,7 +19,6 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.widget.Toast.LENGTH_SHORT;
 
-import static com.android.launcher3.AbstractFloatingView.TYPE_TASK_MENU;
 import static com.android.launcher3.Utilities.comp;
 import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
@@ -67,7 +66,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
@@ -89,7 +87,6 @@
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.RemoteAnimationTargets;
 import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
-import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TaskIconCache;
 import com.android.quickstep.TaskOverlayFactory;
 import com.android.quickstep.TaskThumbnailCache;
@@ -167,13 +164,6 @@
         return deviceProfile.isTablet;
     }
 
-    /**
-     * Should the TaskView scale down to fit whole thumbnail in fullscreen.
-     */
-    public static boolean useFullThumbnail(DeviceProfile deviceProfile) {
-        return deviceProfile.isTablet && !deviceProfile.isTaskbarPresentInApps;
-    }
-
     private static final float EDGE_SCALE_DOWN_FACTOR_CAROUSEL = 0.03f;
     private static final float EDGE_SCALE_DOWN_FACTOR_GRID = 0.00f;
 
@@ -726,10 +716,6 @@
                 return;
             }
 
-            // Reset the minimized state since we force-toggled the minimized state when entering
-            // overview, but never actually finished the recents animation
-            SystemUiProxy.INSTANCE.get(getContext()).setSplitScreenMinimized(false);
-
             mIsClickableAsLiveTile = false;
             RemoteAnimationTargets targets;
             if (remoteTargetHandles.length == 1) {
@@ -1343,7 +1329,7 @@
                 continue;
             }
             for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this,
-                    mActivity.getDeviceProfile(), taskContainer)) {
+                    taskContainer)) {
                 info.addAction(s.createAccessibilityAction(context));
             }
         }
@@ -1381,7 +1367,7 @@
                 continue;
             }
             for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this,
-                    mActivity.getDeviceProfile(), taskContainer)) {
+                    taskContainer)) {
                 if (s.hasHandlerForAction(action)) {
                     s.onClick(this);
                     return true;
@@ -1537,7 +1523,6 @@
     }
 
     public void initiateSplitSelect(SplitPositionOption splitPositionOption) {
-        AbstractFloatingView.closeOpenViews(mActivity, false, TYPE_TASK_MENU);
         getRecentsView().initiateSplitSelect(this, splitPositionOption.stagePosition);
     }
 
@@ -1584,17 +1569,14 @@
             RectF insets = pph.getInsetsToDrawInFullscreen(dp);
 
             float currentInsetsLeft = insets.left * fullscreenProgress;
+            float currentInsetsTop = insets.top * fullscreenProgress;
             float currentInsetsRight = insets.right * fullscreenProgress;
-            float insetsBottom = insets.bottom;
-            if (dp.isTaskbarPresentInApps) {
-                insetsBottom = Math.max(0, insetsBottom - dp.taskbarSize);
-            }
-            mCurrentDrawnInsets.set(currentInsetsLeft, insets.top * fullscreenProgress,
-                    currentInsetsRight, insetsBottom * fullscreenProgress);
-            float fullscreenCornerRadius = dp.isMultiWindowMode ? 0 : mWindowCornerRadius;
+            float currentInsetsBottom = insets.bottom * fullscreenProgress;
+            mCurrentDrawnInsets.set(
+                    currentInsetsLeft, currentInsetsTop, currentInsetsRight, currentInsetsBottom);
 
             mCurrentDrawnCornerRadius =
-                    Utilities.mapRange(fullscreenProgress, mCornerRadius, fullscreenCornerRadius)
+                    Utilities.mapRange(fullscreenProgress, mCornerRadius, mWindowCornerRadius)
                             / parentScale / taskViewScale;
 
             // We scaled the thumbnail to fit the content (excluding insets) within task view width.
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
index d8be307..4eec319 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
@@ -23,6 +23,7 @@
 import static org.mockito.Mockito.when;
 
 import android.os.Handler;
+import android.view.View;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -58,6 +59,8 @@
     TaskbarControllers mockTaskbarControllers;
     @Mock
     TaskbarActivityContext mockTaskbarActivityContext;
+    @Mock
+    View mockView;
 
     private TaskbarNavButtonController mNavButtonController;
 
@@ -76,110 +79,110 @@
 
     @Test
     public void testPressBack() {
-        mNavButtonController.onButtonClick(BUTTON_BACK);
+        mNavButtonController.onButtonClick(BUTTON_BACK, mockView);
         verify(mockSystemUiProxy, times(1)).onBackPressed();
     }
 
     @Test
     public void testPressImeSwitcher() {
-        mNavButtonController.onButtonClick(BUTTON_IME_SWITCH);
+        mNavButtonController.onButtonClick(BUTTON_IME_SWITCH, mockView);
         verify(mockSystemUiProxy, times(1)).onImeSwitcherPressed();
     }
 
     @Test
     public void testPressA11yShortClick() {
-        mNavButtonController.onButtonClick(BUTTON_A11Y);
+        mNavButtonController.onButtonClick(BUTTON_A11Y, mockView);
         verify(mockSystemUiProxy, times(1))
                 .notifyAccessibilityButtonClicked(DISPLAY_ID);
     }
 
     @Test
     public void testPressA11yLongClick() {
-        mNavButtonController.onButtonLongClick(BUTTON_A11Y);
+        mNavButtonController.onButtonLongClick(BUTTON_A11Y, mockView);
         verify(mockSystemUiProxy, times(1)).notifyAccessibilityButtonLongClicked();
     }
 
     @Test
     public void testLongPressHome() {
-        mNavButtonController.onButtonLongClick(BUTTON_HOME);
+        mNavButtonController.onButtonLongClick(BUTTON_HOME, mockView);
         verify(mockSystemUiProxy, times(1)).startAssistant(any());
     }
 
     @Test
     public void testPressHome() {
-        mNavButtonController.onButtonClick(BUTTON_HOME);
+        mNavButtonController.onButtonClick(BUTTON_HOME, mockView);
         verify(mockCommandHelper, times(1)).addCommand(TYPE_HOME);
     }
 
     @Test
     public void testPressRecents() {
-        mNavButtonController.onButtonClick(BUTTON_RECENTS);
+        mNavButtonController.onButtonClick(BUTTON_RECENTS, mockView);
         verify(mockCommandHelper, times(1)).addCommand(TYPE_TOGGLE);
     }
 
     @Test
     public void testPressRecentsWithScreenPinned() {
         mNavButtonController.updateSysuiFlags(SYSUI_STATE_SCREEN_PINNING);
-        mNavButtonController.onButtonClick(BUTTON_RECENTS);
+        mNavButtonController.onButtonClick(BUTTON_RECENTS, mockView);
         verify(mockCommandHelper, times(0)).addCommand(TYPE_TOGGLE);
     }
 
     @Test
     public void testLongPressBackRecentsNotPinned() {
-        mNavButtonController.onButtonLongClick(BUTTON_RECENTS);
-        mNavButtonController.onButtonLongClick(BUTTON_BACK);
+        mNavButtonController.onButtonLongClick(BUTTON_RECENTS, mockView);
+        mNavButtonController.onButtonLongClick(BUTTON_BACK, mockView);
         verify(mockSystemUiProxy, times(0)).stopScreenPinning();
     }
 
     @Test
     public void testLongPressBackRecentsPinned() {
         mNavButtonController.updateSysuiFlags(SYSUI_STATE_SCREEN_PINNING);
-        mNavButtonController.onButtonLongClick(BUTTON_RECENTS);
-        mNavButtonController.onButtonLongClick(BUTTON_BACK);
+        mNavButtonController.onButtonLongClick(BUTTON_RECENTS, mockView);
+        mNavButtonController.onButtonLongClick(BUTTON_BACK, mockView);
         verify(mockSystemUiProxy, times(1)).stopScreenPinning();
     }
 
     @Test
     public void testLongPressBackRecentsTooLongPinned() {
         mNavButtonController.updateSysuiFlags(SYSUI_STATE_SCREEN_PINNING);
-        mNavButtonController.onButtonLongClick(BUTTON_RECENTS);
+        mNavButtonController.onButtonLongClick(BUTTON_RECENTS, mockView);
         try {
             Thread.sleep(SCREEN_PIN_LONG_PRESS_THRESHOLD + 5);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
-        mNavButtonController.onButtonLongClick(BUTTON_BACK);
+        mNavButtonController.onButtonLongClick(BUTTON_BACK, mockView);
         verify(mockSystemUiProxy, times(0)).stopScreenPinning();
     }
 
     @Test
     public void testLongPressBackRecentsMultipleAttemptPinned() {
         mNavButtonController.updateSysuiFlags(SYSUI_STATE_SCREEN_PINNING);
-        mNavButtonController.onButtonLongClick(BUTTON_RECENTS);
+        mNavButtonController.onButtonLongClick(BUTTON_RECENTS, mockView);
         try {
             Thread.sleep(SCREEN_PIN_LONG_PRESS_THRESHOLD + 5);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
-        mNavButtonController.onButtonLongClick(BUTTON_BACK);
+        mNavButtonController.onButtonLongClick(BUTTON_BACK, mockView);
         verify(mockSystemUiProxy, times(0)).stopScreenPinning();
 
         // Try again w/in threshold
-        mNavButtonController.onButtonLongClick(BUTTON_RECENTS);
-        mNavButtonController.onButtonLongClick(BUTTON_BACK);
+        mNavButtonController.onButtonLongClick(BUTTON_RECENTS, mockView);
+        mNavButtonController.onButtonLongClick(BUTTON_BACK, mockView);
         verify(mockSystemUiProxy, times(1)).stopScreenPinning();
     }
 
     @Test
     public void testLongPressHomeScreenPinned() {
         mNavButtonController.updateSysuiFlags(SYSUI_STATE_SCREEN_PINNING);
-        mNavButtonController.onButtonLongClick(BUTTON_HOME);
+        mNavButtonController.onButtonLongClick(BUTTON_HOME, mockView);
         verify(mockSystemUiProxy, times(0)).startAssistant(any());
     }
 
     @Test
     public void testNoCallsToNullLogger() {
-        mNavButtonController.onButtonClick(BUTTON_HOME);
+        mNavButtonController.onButtonClick(BUTTON_HOME, mockView);
         verify(mockStatsLogManager, times(0)).logger();
         verify(mockStatsLogger, times(0)).log(any());
     }
@@ -187,9 +190,9 @@
     @Test
     public void testNoCallsAfterNullingOut() {
         mNavButtonController.init(mockTaskbarControllers);
-        mNavButtonController.onButtonClick(BUTTON_HOME);
+        mNavButtonController.onButtonClick(BUTTON_HOME, mockView);
         mNavButtonController.onDestroy();
-        mNavButtonController.onButtonClick(BUTTON_HOME);
+        mNavButtonController.onButtonClick(BUTTON_HOME, mockView);
         verify(mockStatsLogger, times(1)).log(LAUNCHER_TASKBAR_HOME_BUTTON_TAP);
         verify(mockStatsLogger, times(0)).log(LAUNCHER_TASKBAR_HOME_BUTTON_LONGPRESS);
     }
@@ -197,7 +200,7 @@
     @Test
     public void testLogOnTap() {
         mNavButtonController.init(mockTaskbarControllers);
-        mNavButtonController.onButtonClick(BUTTON_HOME);
+        mNavButtonController.onButtonClick(BUTTON_HOME, mockView);
         verify(mockStatsLogger, times(1)).log(LAUNCHER_TASKBAR_HOME_BUTTON_TAP);
         verify(mockStatsLogger, times(0)).log(LAUNCHER_TASKBAR_HOME_BUTTON_LONGPRESS);
     }
@@ -205,7 +208,7 @@
     @Test
     public void testLogOnLongpress() {
         mNavButtonController.init(mockTaskbarControllers);
-        mNavButtonController.onButtonLongClick(BUTTON_HOME);
+        mNavButtonController.onButtonLongClick(BUTTON_HOME, mockView);
         verify(mockStatsLogger, times(1)).log(LAUNCHER_TASKBAR_HOME_BUTTON_LONGPRESS);
         verify(mockStatsLogger, times(0)).log(LAUNCHER_TASKBAR_HOME_BUTTON_TAP);
     }
@@ -213,11 +216,11 @@
     @Test
     public void testBackOverviewLogOnLongpress() {
         mNavButtonController.init(mockTaskbarControllers);
-        mNavButtonController.onButtonLongClick(BUTTON_RECENTS);
+        mNavButtonController.onButtonLongClick(BUTTON_RECENTS, mockView);
         verify(mockStatsLogger, times(1)).log(LAUNCHER_TASKBAR_OVERVIEW_BUTTON_LONGPRESS);
         verify(mockStatsLogger, times(0)).log(LAUNCHER_TASKBAR_OVERVIEW_BUTTON_TAP);
 
-        mNavButtonController.onButtonLongClick(BUTTON_BACK);
+        mNavButtonController.onButtonLongClick(BUTTON_BACK, mockView);
         verify(mockStatsLogger, times(1)).log(LAUNCHER_TASKBAR_BACK_BUTTON_LONGPRESS);
         verify(mockStatsLogger, times(0)).log(LAUNCHER_TASKBAR_BACK_BUTTON_TAP);
     }
diff --git a/quickstep/tests/src/com/android/quickstep/DeviceProfileQuickstepTest.kt b/quickstep/tests/src/com/android/quickstep/DeviceProfileQuickstepTest.kt
new file mode 100644
index 0000000..20b5a64
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/DeviceProfileQuickstepTest.kt
@@ -0,0 +1,122 @@
+/*
+ * 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.quickstep
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.DeviceProfileBaseTest
+import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.util.WindowBounds
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito.`when` as whenever
+
+/**
+ * Test for [DeviceProfile] quickstep.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceProfileQuickstepTest : DeviceProfileBaseTest() {
+
+    @Test
+    fun getCellLayoutWidthAndHeight_twoPanelLandscapeScalable4By4GridTablet() {
+        val tabletWidth = 2560
+        val tabletHeight = 1600
+        val availableWidth = 2560
+        val availableHeight = 1500
+        windowBounds = WindowBounds(tabletWidth, tabletHeight, availableWidth, availableHeight, 0)
+        useTwoPanels = true
+        whenever(info.isTablet(ArgumentMatchers.any())).thenReturn(true)
+        whenever(info.densityDpi).thenReturn(320)
+        whenever(info.smallestSizeDp(ArgumentMatchers.any())).thenReturn(800f)
+        inv = newScalableInvariantDeviceProfile()
+
+        val dp = newDP()
+
+        assertThat(dp.cellLayoutWidth).isEqualTo(1237)
+        assertThat(dp.cellLayoutHeight).isEqualTo(1215)
+    }
+
+    @Test
+    fun getCellSize_twoPanelLandscapeScalable4By4GridTablet() {
+        val tabletWidth = 2560
+        val tabletHeight = 1600
+        val availableWidth = 2560
+        val availableHeight = 1500
+        windowBounds = WindowBounds(tabletWidth, tabletHeight, availableWidth, availableHeight, 0)
+        useTwoPanels = true
+        whenever(info.isTablet(ArgumentMatchers.any())).thenReturn(true)
+        whenever(info.densityDpi).thenReturn(320)
+        whenever(info.smallestSizeDp(ArgumentMatchers.any())).thenReturn(800f)
+        inv = newScalableInvariantDeviceProfile()
+
+        val dp = newDP()
+
+        assertThat(dp.getCellSize().y).isEqualTo(260)
+        assertThat(dp.getCellSize().x).isEqualTo(259)
+    }
+
+    @Test
+    fun getPanelCount_twoPanelLandscapeScalable4By4GridTablet() {
+        val tabletWidth = 2560
+        val tabletHeight = 1600
+        val availableWidth = 2560
+        val availableHeight = 1500
+        windowBounds = WindowBounds(tabletWidth, tabletHeight, availableWidth, availableHeight, 0)
+        useTwoPanels = true
+        whenever(info.isTablet(ArgumentMatchers.any())).thenReturn(true)
+        whenever(info.densityDpi).thenReturn(320)
+        whenever(info.smallestSizeDp(ArgumentMatchers.any())).thenReturn(800f)
+        inv = newScalableInvariantDeviceProfile()
+
+        val dp = newDP()
+
+        assertThat(dp.panelCount).isEqualTo(2)
+    }
+
+    @Test
+    fun getWorkspaceSpringLoadShrunkTopBottom_landscapePhoneVerticalBar() {
+        inv = newScalableInvariantDeviceProfile()
+        initializeVarsForPhone(true)
+        inv = newScalableInvariantDeviceProfile().apply {
+            deviceType = InvariantDeviceProfile.TYPE_PHONE
+            transposeLayoutWithOrientation = true
+        }
+
+        val dp = newDP()
+
+        assertThat(dp.isVerticalBarLayout).isEqualTo(true)
+        assertThat(dp.cellLayoutSpringLoadShrunkTop).isEqualTo(168)
+        assertThat(dp.cellLayoutSpringLoadShrunkBottom).isEqualTo(1358)
+    }
+
+    @Test
+    fun getWorkspaceSpringLoadShrunkTopBottom_portraitPhone() {
+        inv = newScalableInvariantDeviceProfile()
+        initializeVarsForPhone()
+        inv = newScalableInvariantDeviceProfile().apply {
+            deviceType = InvariantDeviceProfile.TYPE_PHONE
+        }
+
+        val dp = newDP()
+
+        assertThat(dp.isVerticalBarLayout).isEqualTo(false)
+        assertThat(dp.cellLayoutSpringLoadShrunkTop).isEqualTo(364)
+        assertThat(dp.cellLayoutSpringLoadShrunkBottom).isEqualTo(2185)
+    }
+}
\ No newline at end of file
diff --git a/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java b/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
index 9e5d958..1c15e1e 100644
--- a/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
+++ b/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
@@ -35,7 +35,6 @@
 import android.util.ArrayMap;
 import android.util.DisplayMetrics;
 import android.util.Size;
-import android.view.Display;
 import android.view.MotionEvent;
 import android.view.Surface;
 
@@ -290,15 +289,17 @@
     private DisplayController.Info createDisplayInfo(Size screenSize, int rotation) {
         Point displaySize = new Point(screenSize.getWidth(), screenSize.getHeight());
         RotationUtils.rotateSize(displaySize, rotation);
-        CachedDisplayInfo cdi = new CachedDisplayInfo(displaySize, rotation);
-        WindowBounds wm = new WindowBounds(
+        CachedDisplayInfo cachedDisplayInfo = new CachedDisplayInfo(displaySize, rotation);
+        WindowBounds windowBounds = new WindowBounds(
                 new Rect(0, 0, displaySize.x, displaySize.y),
                 new Rect());
         WindowManagerProxy wmProxy = mock(WindowManagerProxy.class);
-        doReturn(cdi).when(wmProxy).getDisplayInfo(any(), any());
-        doReturn(wm).when(wmProxy).getRealBounds(any(), any(), any());
+        doReturn(cachedDisplayInfo).when(wmProxy).getDisplayInfo(any());
+        doReturn(windowBounds).when(wmProxy).getRealBounds(any(), any());
+        ArrayMap<CachedDisplayInfo, WindowBounds[]> internalDisplayBounds = new ArrayMap<>();
+        doReturn(internalDisplayBounds).when(wmProxy).estimateInternalDisplayBounds(any());
         return new DisplayController.Info(
-                getApplicationContext(), mock(Display.class), wmProxy, new ArrayMap<>());
+                getApplicationContext(), wmProxy, new ArrayMap<>());
     }
 
     private float generateTouchRegionHeight(Size screenSize, int rotation) {
diff --git a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
index 6ec6269..401b967 100644
--- a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
+++ b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
@@ -16,8 +16,6 @@
 
 package com.android.quickstep;
 
-import android.content.Intent;
-
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -43,7 +41,7 @@
         // b/143488140
         mLauncher.goHome();
         // Start an activity where the gestures start.
-        startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
+        startTestActivity(2);
     }
 
     private void runTest(String... eventSequence) {
diff --git a/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java b/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
index 7d414f4..d43aafa 100644
--- a/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
+++ b/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
@@ -23,8 +23,6 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.util.ArrayMap;
-import android.util.Pair;
-import android.view.Display;
 import android.view.Surface;
 import android.view.SurfaceControl;
 
@@ -148,7 +146,7 @@
                 int rotation = mDisplaySize.x > mDisplaySize.y
                         ? Surface.ROTATION_90 : Surface.ROTATION_0;
                 CachedDisplayInfo cdi =
-                        new CachedDisplayInfo("test-display", mDisplaySize, rotation , new Rect());
+                        new CachedDisplayInfo(mDisplaySize, rotation, new Rect());
                 WindowBounds wm = new WindowBounds(
                         new Rect(0, 0, mDisplaySize.x, mDisplaySize.y),
                         mDisplayInsets);
@@ -164,15 +162,15 @@
                 }
 
                 WindowManagerProxy wmProxy = mock(WindowManagerProxy.class);
-                doReturn(cdi).when(wmProxy).getDisplayInfo(any(), any());
-                doReturn(wm).when(wmProxy).getRealBounds(any(), any(), any());
+                doReturn(cdi).when(wmProxy).getDisplayInfo(any());
+                doReturn(wm).when(wmProxy).getRealBounds(any(), any());
 
-                ArrayMap<String, Pair<CachedDisplayInfo, WindowBounds[]>> perDisplayBoundsCache =
+                ArrayMap<CachedDisplayInfo, WindowBounds[]> perDisplayBoundsCache =
                         new ArrayMap<>();
-                perDisplayBoundsCache.put(cdi.id, Pair.create(cdi.normalize(), allBounds));
+                perDisplayBoundsCache.put(cdi.normalize(), allBounds);
 
                 DisplayController.Info mockInfo = new Info(
-                        helper.sandboxContext, mock(Display.class), wmProxy, perDisplayBoundsCache);
+                        helper.sandboxContext, wmProxy, perDisplayBoundsCache);
 
                 DisplayController controller =
                         DisplayController.INSTANCE.get(helper.sandboxContext);
diff --git a/res/drawable/bg_work_apps_paused_action_button.xml b/res/drawable/bg_work_apps_paused_action_button.xml
new file mode 100644
index 0000000..74d4693
--- /dev/null
+++ b/res/drawable/bg_work_apps_paused_action_button.xml
@@ -0,0 +1,31 @@
+<?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.
+-->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="@color/accent_ripple_color">
+    <item android:id="@android:id/mask">
+        <shape android:shape="rectangle">
+            <corners android:radius="@dimen/rounded_button_radius" />
+            <solid android:color="@android:color/white" />
+        </shape>
+    </item>
+
+    <item android:id="@android:id/background">
+        <shape android:shape="rectangle">
+            <solid android:color="?android:attr/colorControlHighlight" />
+            <corners android:radius="@dimen/rounded_button_radius" />
+        </shape>
+    </item>
+</ripple>
\ No newline at end of file
diff --git a/res/drawable/work_apps_toggle_background.xml b/res/drawable/work_apps_toggle_background.xml
index a47c8fe..6ad6c82 100644
--- a/res/drawable/work_apps_toggle_background.xml
+++ b/res/drawable/work_apps_toggle_background.xml
@@ -13,16 +13,8 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_enabled="false">
-        <shape android:shape="rectangle">
-            <corners android:radius="@dimen/work_fab_radius" />
-            <solid android:color="?android:attr/colorControlHighlight" />
-            <padding
-                android:left="@dimen/work_profile_footer_padding"
-                android:right="@dimen/work_profile_footer_padding" />
-        </shape>
-    </item>
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="@color/accent_ripple_color">
     <item>
         <shape android:shape="rectangle">
             <corners android:radius="@dimen/work_fab_radius" />
@@ -32,4 +24,4 @@
                 android:right="@dimen/work_profile_footer_padding" />
         </shape>
     </item>
-</selector>
+</ripple>
diff --git a/res/layout/widgets_full_sheet_paged_view.xml b/res/layout/widgets_full_sheet_paged_view.xml
index dfe226a..24028fa 100644
--- a/res/layout/widgets_full_sheet_paged_view.xml
+++ b/res/layout/widgets_full_sheet_paged_view.xml
@@ -16,37 +16,35 @@
 <merge xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:launcher="http://schemas.android.com/apk/res-auto">
 
-    <com.android.launcher3.workprofile.PersonalWorkPagedView
+    <com.android.launcher3.widget.picker.WidgetPagedView
         android:id="@+id/widgets_view_pager"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:clipToPadding="false"
         android:layout_below="@id/collapse_handle"
         android:descendantFocusability="afterDescendants"
-        launcher:pageIndicator="@+id/tabs">
+        android:paddingHorizontal="@dimen/widget_list_horizontal_margin"
+        launcher:pageIndicator="@+id/tabs" >
 
         <com.android.launcher3.widget.picker.WidgetsRecyclerView
             android:id="@+id/primary_widgets_list_view"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
-            android:paddingHorizontal="@dimen/widget_list_horizontal_margin"
             android:clipToPadding="false" />
 
         <com.android.launcher3.widget.picker.WidgetsRecyclerView
             android:id="@+id/work_widgets_list_view"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
-            android:paddingHorizontal="@dimen/widget_list_horizontal_margin"
             android:clipToPadding="false" />
 
-    </com.android.launcher3.workprofile.PersonalWorkPagedView>
+    </com.android.launcher3.widget.picker.WidgetPagedView>
 
     <!-- SearchAndRecommendationsView contains the tab layout as well -->
     <com.android.launcher3.widget.picker.SearchAndRecommendationsView
         android:id="@+id/search_and_recommendations_container"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
         android:layout_below="@id/collapse_handle"
         android:paddingBottom="0dp"
         android:orientation="vertical">
@@ -58,6 +56,7 @@
             android:gravity="center_horizontal"
             android:textSize="24sp"
             android:layout_marginTop="24dp"
+            android:paddingHorizontal="@dimen/widget_list_horizontal_margin"
             android:textColor="?android:attr/textColorSecondary"
             android:text="@string/widget_button_text"/>
 
@@ -68,6 +67,7 @@
             android:elevation="0.1dp"
             android:background="?android:attr/colorBackground"
             android:paddingBottom="8dp"
+            android:paddingHorizontal="@dimen/widget_list_horizontal_margin"
             android:clipToPadding="false">
             <include layout="@layout/widgets_search_bar" />
         </FrameLayout>
@@ -79,6 +79,7 @@
             android:layout_marginTop="8dp"
             android:background="@drawable/widgets_recommendation_background"
             android:paddingVertical="@dimen/recommended_widgets_table_vertical_padding"
+            android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
             android:visibility="gone" />
 
         <com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip
diff --git a/res/layout/widgets_full_sheet_recyclerview.xml b/res/layout/widgets_full_sheet_recyclerview.xml
index 6a5d6cb..f4b5a0a 100644
--- a/res/layout/widgets_full_sheet_recyclerview.xml
+++ b/res/layout/widgets_full_sheet_recyclerview.xml
@@ -19,7 +19,7 @@
         android:layout_below="@id/collapse_handle"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:paddingHorizontal="@dimen/widget_list_horizontal_margin"
+        android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
         android:clipToPadding="false" />
 
     <!-- SearchAndRecommendationsView without the tab layout as well -->
@@ -27,7 +27,6 @@
         android:id="@+id/search_and_recommendations_container"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
         android:layout_below="@id/collapse_handle"
         android:paddingBottom="16dp"
         android:orientation="vertical">
@@ -40,6 +39,7 @@
             android:textSize="24sp"
             android:layout_marginTop="24dp"
             android:textColor="?android:attr/textColorSecondary"
+            android:paddingHorizontal="@dimen/widget_list_horizontal_margin"
             android:text="@string/widget_button_text"/>
 
         <FrameLayout
@@ -48,6 +48,7 @@
             android:layout_height="wrap_content"
             android:elevation="0.1dp"
             android:background="?android:attr/colorBackground"
+            android:paddingHorizontal="@dimen/widget_list_horizontal_margin"
             android:paddingBottom="8dp"
             android:clipToPadding="false">
             <include layout="@layout/widgets_search_bar" />
@@ -60,6 +61,7 @@
             android:layout_marginTop="8dp"
             android:background="@drawable/widgets_recommendation_background"
             android:paddingVertical="@dimen/recommended_widgets_table_vertical_padding"
+            android:paddingHorizontal="@dimen/widget_list_horizontal_margin"
             android:visibility="gone" />
     </com.android.launcher3.widget.picker.SearchAndRecommendationsView>
 
diff --git a/res/layout/widgets_list_row_header.xml b/res/layout/widgets_list_row_header.xml
index 3cdc2e8..35bea27 100644
--- a/res/layout/widgets_list_row_header.xml
+++ b/res/layout/widgets_list_row_header.xml
@@ -19,7 +19,6 @@
     android:id="@+id/widgets_list_header"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:paddingVertical="@dimen/widget_list_header_view_vertical_padding"
     android:orientation="horizontal"
     android:importantForAccessibility="yes"
     android:focusable="true"
diff --git a/res/layout/work_apps_paused.xml b/res/layout/work_apps_paused.xml
index 79bce70..f614d9b 100644
--- a/res/layout/work_apps_paused.xml
+++ b/res/layout/work_apps_paused.xml
@@ -48,7 +48,7 @@
         android:textColor="?attr/workProfileOverlayTextColor"
         android:text="@string/work_apps_enable_btn_text"
         android:textAlignment="center"
-        android:background="@drawable/rounded_action_button"
+        android:background="@drawable/bg_work_apps_paused_action_button"
         android:paddingStart="16dp"
         android:paddingEnd="16dp"
         android:textSize="14sp" />
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 55139b3..445bfda 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Item is verwyder"</string>
     <string name="undo" msgid="4151576204245173321">"Ontdoen"</string>
     <string name="action_move" msgid="4339390619886385032">"Skuif item"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Skuif na ry <xliff:g id="NUMBER_0">%1$s</xliff:g> kolom <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Skuif na ry <xliff:g id="NUMBER_0">%1$s</xliff:g> kolom <xliff:g id="NUMBER_1">%2$s</xliff:g> in <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Skuif na posisie <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Skuif na gunstelingposisie <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Item geskuif"</string>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index 6b6147b..e5a7356 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"ንጥል ነገር ተንቀሳቅሷል"</string>
     <string name="undo" msgid="4151576204245173321">"ቀልብስ"</string>
     <string name="action_move" msgid="4339390619886385032">"ንጥልን አንቀሳቅስ"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"ወደ ረድፍ <xliff:g id="NUMBER_0">%1$s</xliff:g> ዓምድ <xliff:g id="NUMBER_1">%2$s</xliff:g> አንቀሳቅስ"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"በ<xliff:g id="STRING">%3$s</xliff:g> ውስጥ ወደ ረድፍ <xliff:g id="NUMBER_0">%1$s</xliff:g> ዓምድ <xliff:g id="NUMBER_1">%2$s</xliff:g> ይውሰዱ"</string>
     <string name="move_to_position" msgid="6750008980455459790">"ወደ አቀማመጥ <xliff:g id="NUMBER">%1$s</xliff:g> አንቀሳቅስ"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"ወደ ተወዳጆች አቀማመጥ <xliff:g id="NUMBER">%1$s</xliff:g> አንቀሳቅስ"</string>
     <string name="item_moved" msgid="4606538322571412879">"ንጥል ተንቀሳቅሷል"</string>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 0e48179..a4f7c03 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"تمّت إزالة العنصر."</string>
     <string name="undo" msgid="4151576204245173321">"تراجع"</string>
     <string name="action_move" msgid="4339390619886385032">"نقل العنصر"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"نقل إلى الصف <xliff:g id="NUMBER_0">%1$s</xliff:g> العمود <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"انتقل إلى الصف <xliff:g id="NUMBER_0">%1$s</xliff:g> العمود <xliff:g id="NUMBER_1">%2$s</xliff:g> في <xliff:g id="STRING">%3$s</xliff:g>."</string>
     <string name="move_to_position" msgid="6750008980455459790">"نقل إلى الموضع <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"نقل إلى الموضع المفضل <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"تم نقل العنصر"</string>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index 5fc8ca6..beb9619 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"বস্তুটো আঁতৰোৱা হ’ল"</string>
     <string name="undo" msgid="4151576204245173321">"আনডু কৰক"</string>
     <string name="action_move" msgid="4339390619886385032">"বস্তু স্থানান্তৰ কৰক"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"শাৰী <xliff:g id="NUMBER_0">%1$s</xliff:g> স্তম্ভ <xliff:g id="NUMBER_1">%2$s</xliff:g>লৈ স্থানান্তৰিত কৰক"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g>ত <xliff:g id="NUMBER_0">%1$s</xliff:g> নম্বৰ শাৰী <xliff:g id="NUMBER_1">%2$s</xliff:g> নম্বৰ স্তম্ভলৈ লৈ যাওক"</string>
     <string name="move_to_position" msgid="6750008980455459790">"পছন্দৰ অৱস্থান <xliff:g id="NUMBER">%1$s</xliff:g>লৈ স্থানান্তৰিত কৰক"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"পছন্দৰ অৱস্থান <xliff:g id="NUMBER">%1$s</xliff:g>লৈ স্থানান্তৰিত কৰক"</string>
     <string name="item_moved" msgid="4606538322571412879">"বস্তুটো স্থানান্তৰ কৰা হ’ল"</string>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index ad72424..93e6f08 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Element silindi"</string>
     <string name="undo" msgid="4151576204245173321">"Ləğv edin"</string>
     <string name="action_move" msgid="4339390619886385032">"Elementi köçürün"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Sıra <xliff:g id="NUMBER_0">%1$s</xliff:g> sütun <xliff:g id="NUMBER_1">%2$s</xliff:g> köçürün"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="NUMBER_0">%1$s</xliff:g> saylı sətir, <xliff:g id="NUMBER_1">%2$s</xliff:g> saylı sütuna (<xliff:g id="STRING">%3$s</xliff:g>) köçürün"</string>
     <string name="move_to_position" msgid="6750008980455459790">"<xliff:g id="NUMBER">%1$s</xliff:g> mövqeyinə köçürün"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"<xliff:g id="NUMBER">%1$s</xliff:g> sevimlilər mövqeyinə köçürün"</string>
     <string name="item_moved" msgid="4606538322571412879">"Elementin yeri dəyişildi"</string>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index d7e85d9..ded4dc9 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Stavka je uklonjena"</string>
     <string name="undo" msgid="4151576204245173321">"Opozovi"</string>
     <string name="action_move" msgid="4339390619886385032">"Premesti stavku"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Premesti u red <xliff:g id="NUMBER_0">%1$s</xliff:g> i kolonu <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Premestite u red <xliff:g id="NUMBER_0">%1$s</xliff:g> kolonu <xliff:g id="NUMBER_1">%2$s</xliff:g> na <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Premesti na <xliff:g id="NUMBER">%1$s</xliff:g>. poziciju"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Premesti na <xliff:g id="NUMBER">%1$s</xliff:g>. poziciju u omiljenim"</string>
     <string name="item_moved" msgid="4606538322571412879">"Stavka je premeštena"</string>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index a845f9a..7fa8341 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Элемент выдалены"</string>
     <string name="undo" msgid="4151576204245173321">"Адрабіць"</string>
     <string name="action_move" msgid="4339390619886385032">"Перамясціць элемент"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Перамясціць у радок <xliff:g id="NUMBER_0">%1$s</xliff:g> слупок <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Перайсці да радка <xliff:g id="NUMBER_0">%1$s</xliff:g> у слупку <xliff:g id="NUMBER_1">%2$s</xliff:g> на старонцы <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Перамясціць у пазіцыю <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Перамясціць у абранае, у пазіцыю <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Элемент перамешчаны"</string>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 0c7fcd3..62b7796 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Елементът е премахнат"</string>
     <string name="undo" msgid="4151576204245173321">"Отмяна"</string>
     <string name="action_move" msgid="4339390619886385032">"Преместване на елемента"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Преместване към ред <xliff:g id="NUMBER_0">%1$s</xliff:g>, колона <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Преместване на <xliff:g id="STRING">%3$s</xliff:g> – ред <xliff:g id="NUMBER_0">%1$s</xliff:g>, колона <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Преместване към позиция <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Преместване към позиция <xliff:g id="NUMBER">%1$s</xliff:g> в любимите"</string>
     <string name="item_moved" msgid="4606538322571412879">"Елементът е преместен"</string>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index 5c8c342..1be272b 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"আইটেম সরানো হয়েছে"</string>
     <string name="undo" msgid="4151576204245173321">"ফিরিয়ে আনুন"</string>
     <string name="action_move" msgid="4339390619886385032">"আইটেম সরান"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"সারি <xliff:g id="NUMBER_0">%1$s</xliff:g> কলাম <xliff:g id="NUMBER_1">%2$s</xliff:g> এ সরান"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g>-এ সারি <xliff:g id="NUMBER_0">%1$s</xliff:g> কলাম <xliff:g id="NUMBER_1">%2$s</xliff:g> সরান"</string>
     <string name="move_to_position" msgid="6750008980455459790">"অবস্থানে সরান <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"পছন্দসই অবস্থানে সরান <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"আইটেম সরানো হয়েছে"</string>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 173518f..639b6b3 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Stavka je uklonjena"</string>
     <string name="undo" msgid="4151576204245173321">"Poništi"</string>
     <string name="action_move" msgid="4339390619886385032">"Premjesti stavku"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Pomjeri stavku u red <xliff:g id="NUMBER_0">%1$s</xliff:g> kolonu <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Premještanje u red <xliff:g id="NUMBER_0">%1$s</xliff:g>, kolonu <xliff:g id="NUMBER_1">%2$s</xliff:g> na <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Pomjeri stavku na poziciju <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Pomjeri stavku na poziciju <xliff:g id="NUMBER">%1$s</xliff:g> među omiljenim"</string>
     <string name="item_moved" msgid="4606538322571412879">"Stavka je premještena"</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index e6a4af2..ee35fd3 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"S\'ha suprimit l\'element"</string>
     <string name="undo" msgid="4151576204245173321">"Desfés"</string>
     <string name="action_move" msgid="4339390619886385032">"Desplaça l\'element"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Desplaça l\'element a la fila <xliff:g id="NUMBER_0">%1$s</xliff:g> i la columna <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Mou a la fila <xliff:g id="NUMBER_0">%1$s</xliff:g>, columna <xliff:g id="NUMBER_1">%2$s</xliff:g> a <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Desplaça l\'element a la posició <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Desplaça l\'element a la posició de preferits <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Element desplaçat"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 8b3ef2d..1b418c6 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Položka byla odstraněna"</string>
     <string name="undo" msgid="4151576204245173321">"Zpět"</string>
     <string name="action_move" msgid="4339390619886385032">"Přesunout položku"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Přesunout na řádek <xliff:g id="NUMBER_0">%1$s</xliff:g> do sloupce <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Přesunout na řádek <xliff:g id="NUMBER_0">%1$s</xliff:g>, sloupec <xliff:g id="NUMBER_1">%2$s</xliff:g> na ploše <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Přesunout na pozici <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Přesunout do oblíbených položek na pozici <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Položka byla přesunuta"</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 8160465..5cdadcd 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Elementet er fjernet"</string>
     <string name="undo" msgid="4151576204245173321">"Fortryd"</string>
     <string name="action_move" msgid="4339390619886385032">"Flyt element"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Flyt til række <xliff:g id="NUMBER_0">%1$s</xliff:g>, kolonne <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Flyt til række <xliff:g id="NUMBER_0">%1$s</xliff:g> kolonne <xliff:g id="NUMBER_1">%2$s</xliff:g> i <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Flyt til position <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Flyt til foretrukne position <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Elementet blev flyttet"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 912e7fa..b75ef9d 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Element entfernt"</string>
     <string name="undo" msgid="4151576204245173321">"Rückgängig"</string>
     <string name="action_move" msgid="4339390619886385032">"Element verschieben"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"In Zeile <xliff:g id="NUMBER_0">%1$s</xliff:g>, Spalte <xliff:g id="NUMBER_1">%2$s</xliff:g> verschoben"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"In <xliff:g id="STRING">%3$s</xliff:g> zu Zeile <xliff:g id="NUMBER_0">%1$s</xliff:g> Spalte <xliff:g id="NUMBER_1">%2$s</xliff:g> bewegen"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Auf Position <xliff:g id="NUMBER">%1$s</xliff:g> verschoben"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Auf Favoritenposition <xliff:g id="NUMBER">%1$s</xliff:g> verschoben"</string>
     <string name="item_moved" msgid="4606538322571412879">"Element verschoben"</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 0f613de..99c9e23 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Το στοιχείο καταργήθηκε"</string>
     <string name="undo" msgid="4151576204245173321">"Αναίρεση"</string>
     <string name="action_move" msgid="4339390619886385032">"Μετακίνηση στοιχείου"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Μετακίνηση στη σειρά <xliff:g id="NUMBER_0">%1$s</xliff:g>, στήλη <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Μετακίνηση στη σειρά <xliff:g id="NUMBER_0">%1$s</xliff:g> στήλη <xliff:g id="NUMBER_1">%2$s</xliff:g> στην <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Μετακίνηση στη θέση <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Μετακίνηση στη θέση <xliff:g id="NUMBER">%1$s</xliff:g> στα αγαπημένα"</string>
     <string name="item_moved" msgid="4606538322571412879">"Το στοιχείο καταργήθηκε"</string>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index a78c7f8..97f0528 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Item removed"</string>
     <string name="undo" msgid="4151576204245173321">"Undo"</string>
     <string name="action_move" msgid="4339390619886385032">"Move item"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Move to row <xliff:g id="NUMBER_0">%1$s</xliff:g> column <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Move to row <xliff:g id="NUMBER_0">%1$s</xliff:g> column <xliff:g id="NUMBER_1">%2$s</xliff:g> in <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Move to position <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Move to favourites position <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Item moved"</string>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index a78c7f8..97f0528 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Item removed"</string>
     <string name="undo" msgid="4151576204245173321">"Undo"</string>
     <string name="action_move" msgid="4339390619886385032">"Move item"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Move to row <xliff:g id="NUMBER_0">%1$s</xliff:g> column <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Move to row <xliff:g id="NUMBER_0">%1$s</xliff:g> column <xliff:g id="NUMBER_1">%2$s</xliff:g> in <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Move to position <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Move to favourites position <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Item moved"</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index a78c7f8..97f0528 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Item removed"</string>
     <string name="undo" msgid="4151576204245173321">"Undo"</string>
     <string name="action_move" msgid="4339390619886385032">"Move item"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Move to row <xliff:g id="NUMBER_0">%1$s</xliff:g> column <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Move to row <xliff:g id="NUMBER_0">%1$s</xliff:g> column <xliff:g id="NUMBER_1">%2$s</xliff:g> in <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Move to position <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Move to favourites position <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Item moved"</string>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index a78c7f8..97f0528 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Item removed"</string>
     <string name="undo" msgid="4151576204245173321">"Undo"</string>
     <string name="action_move" msgid="4339390619886385032">"Move item"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Move to row <xliff:g id="NUMBER_0">%1$s</xliff:g> column <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Move to row <xliff:g id="NUMBER_0">%1$s</xliff:g> column <xliff:g id="NUMBER_1">%2$s</xliff:g> in <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Move to position <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Move to favourites position <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Item moved"</string>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
index d6951ad..4977beb 100644
--- a/res/values-en-rXC/strings.xml
+++ b/res/values-en-rXC/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‎‎‏‎‏‏‏‏‎‎‏‏‏‏‏‏‎‎‏‎‎‏‎‎‎‏‏‎‎‏‎‎‎‎‎‏‎‎‎‎‏‎‏‏‏‎‏‎‎‏‏‏‎‏‏‎‎‏‎‏‏‏‎Item removed‎‏‎‎‏‎"</string>
     <string name="undo" msgid="4151576204245173321">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‎‏‏‏‎‏‎‏‎‏‏‏‎‎‏‎‎‎‏‏‏‏‏‏‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‎‎‏‎‎‏‎‎‏‎Undo‎‏‎‎‏‎"</string>
     <string name="action_move" msgid="4339390619886385032">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‏‏‎‎‎‎‏‏‏‎‎‎‏‎‎‏‏‏‎‎‏‏‎‎‎‏‏‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‏‏‏‏‎‎‎‏‎‎‎‎Move item‎‏‎‎‏‎"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‎‏‏‏‎‏‎‏‎‎‏‏‎‏‎‏‏‏‎‏‏‎‎‏‎‏‎‎‏‎‎‎‎‏‎‏‎‎‎‏‏‎‎‎‎‏‏‎‎‏‎‏‏‏‏‏‎‎‏‏‎Move to row ‎‏‎‎‏‏‎<xliff:g id="NUMBER_0">%1$s</xliff:g>‎‏‎‎‏‏‏‎ column ‎‏‎‎‏‏‎<xliff:g id="NUMBER_1">%2$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‏‏‎‏‏‎‎‏‏‏‏‏‏‎‎‏‎‏‎‏‎‏‏‎‏‏‎‎‎‎‎‎‎‏‏‎‏‎‎‏‏‎‎‏‏‏‎‏‎‏‎‏‎‎‏‎Move to row ‎‏‎‎‏‏‎<xliff:g id="NUMBER_0">%1$s</xliff:g>‎‏‎‎‏‏‏‎ column ‎‏‎‎‏‏‎<xliff:g id="NUMBER_1">%2$s</xliff:g>‎‏‎‎‏‏‏‎ in ‎‏‎‎‏‏‎<xliff:g id="STRING">%3$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="move_to_position" msgid="6750008980455459790">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‏‎‏‎‏‏‎‎‏‏‎‏‏‎‎‏‎‏‏‎‎‏‏‏‏‎‎‏‎‏‏‎‏‎‏‏‏‏‏‎‏‎‎‎‏‎‏‏‏‏‎‎‏‏‏‎‎Move to position ‎‏‎‎‏‏‎<xliff:g id="NUMBER">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‏‎‏‏‏‎‏‏‏‎‎‏‏‎‎‏‎‏‎‏‏‎‎‏‏‏‎‎‏‎‎‏‎‎‏‎‎‏‏‏‎‏‏‎‏‎‎‎‎‏‎‎‎‎‎‎Move to favorites position ‎‏‎‎‏‏‎<xliff:g id="NUMBER">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="item_moved" msgid="4606538322571412879">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‏‏‎‏‏‎‎‎‏‏‎‎‏‎‏‎‏‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‎‎‏‎‏‏‎‎‎‏‏‏‏‎Item moved‎‏‎‎‏‎"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 3897974..3ed312b 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Se eliminó el elemento."</string>
     <string name="undo" msgid="4151576204245173321">"Deshacer"</string>
     <string name="action_move" msgid="4339390619886385032">"Mover elemento"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Mover a fila <xliff:g id="NUMBER_0">%1$s</xliff:g>, columna <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Mover a la fila <xliff:g id="NUMBER_0">%1$s</xliff:g>, columna <xliff:g id="NUMBER_1">%2$s</xliff:g> en <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Mover a la posición número <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Mover a la posición de favoritos número <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Elemento movido"</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 823e5a7..567ff0d 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Elemento quitado"</string>
     <string name="undo" msgid="4151576204245173321">"Deshacer"</string>
     <string name="action_move" msgid="4339390619886385032">"Mover elemento"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Mover a la fila <xliff:g id="NUMBER_0">%1$s</xliff:g>, columna <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Mover a la fila <xliff:g id="NUMBER_0">%1$s</xliff:g>, columna <xliff:g id="NUMBER_1">%2$s</xliff:g> en <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Mover a la posición número <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Mover a la posición número <xliff:g id="NUMBER">%1$s</xliff:g> de favoritos"</string>
     <string name="item_moved" msgid="4606538322571412879">"Elemento movido"</string>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index bd2190a..c34a003 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Üksus eemaldati"</string>
     <string name="undo" msgid="4151576204245173321">"Võta tagasi"</string>
     <string name="action_move" msgid="4339390619886385032">"Teisalda üksus"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Teisaldamine <xliff:g id="NUMBER_0">%1$s</xliff:g>. rea <xliff:g id="NUMBER_1">%2$s</xliff:g>. veergu"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Teisalda reale <xliff:g id="NUMBER_0">%1$s</xliff:g> veerus <xliff:g id="NUMBER_1">%2$s</xliff:g> kohas <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Teisaldamine <xliff:g id="NUMBER">%1$s</xliff:g>. positsioonile"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Teisaldamine lemmikute <xliff:g id="NUMBER">%1$s</xliff:g>. positsioonile"</string>
     <string name="item_moved" msgid="4606538322571412879">"Üksus teisaldati"</string>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index c1eaea8..55e0c3c 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Kendu da elementua"</string>
     <string name="undo" msgid="4151576204245173321">"Desegin"</string>
     <string name="action_move" msgid="4339390619886385032">"Mugitu elementua"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Eraman <xliff:g id="NUMBER_0">%1$s</xliff:g>. errenkadara, <xliff:g id="NUMBER_1">%2$s</xliff:g>. zutabera"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Joan <xliff:g id="NUMBER_0">%1$s</xliff:g>garren errenkadako <xliff:g id="NUMBER_1">%2$s</xliff:g>garren zutabera, <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Eraman <xliff:g id="NUMBER">%1$s</xliff:g>. postura"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Eraman gogokoen <xliff:g id="NUMBER">%1$s</xliff:g>. postura"</string>
     <string name="item_moved" msgid="4606538322571412879">"Elementua mugitu da"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index d23eac2..12db5bd 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"مورد حذف شد"</string>
     <string name="undo" msgid="4151576204245173321">"واگرد"</string>
     <string name="action_move" msgid="4339390619886385032">"انتقال مورد"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"انتقال به سطر <xliff:g id="NUMBER_0">%1$s</xliff:g> ستون <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"انتقال به ردیف <xliff:g id="NUMBER_0">%1$s</xliff:g> ستون <xliff:g id="NUMBER_1">%2$s</xliff:g> در <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"انتقال به موقعیت <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"انتقال به موقعیت دلخواه <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"مورد منتقل شد"</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index ccc22f0..135c452 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Kohde poistettiin"</string>
     <string name="undo" msgid="4151576204245173321">"Kumoa"</string>
     <string name="action_move" msgid="4339390619886385032">"Siirrä kohde"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Siirrä rivin <xliff:g id="NUMBER_0">%1$s</xliff:g> sarakkeeseen <xliff:g id="NUMBER_1">%2$s</xliff:g>."</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Siirrä riviin <xliff:g id="NUMBER_0">%1$s</xliff:g> sarakkeeseen <xliff:g id="NUMBER_1">%2$s</xliff:g> näytöllä <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Siirrä kohtaan <xliff:g id="NUMBER">%1$s</xliff:g>."</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Siirrä suosikkien kohtaan <xliff:g id="NUMBER">%1$s</xliff:g>."</string>
     <string name="item_moved" msgid="4606538322571412879">"Kohde on siirretty."</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index 11bcae1..446a507 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Élément retiré"</string>
     <string name="undo" msgid="4151576204245173321">"Annuler"</string>
     <string name="action_move" msgid="4339390619886385032">"Déplacer l\'élément"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Déplacer vers rangée <xliff:g id="NUMBER_0">%1$s</xliff:g> colonne <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Déplacer à la ligne <xliff:g id="NUMBER_0">%1$s</xliff:g> colonne <xliff:g id="NUMBER_1">%2$s</xliff:g> dans <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Déplacer vers la position <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Déplacer vers la position <xliff:g id="NUMBER">%1$s</xliff:g> dans les favoris"</string>
     <string name="item_moved" msgid="4606538322571412879">"Élément déplacé"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 9b30b1e..1d623b1 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Élément supprimé"</string>
     <string name="undo" msgid="4151576204245173321">"Annuler"</string>
     <string name="action_move" msgid="4339390619886385032">"Déplacer l\'élément"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Déplacer vers la ligne <xliff:g id="NUMBER_0">%1$s</xliff:g>, colonne <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Déplacer vers la ligne <xliff:g id="NUMBER_0">%1$s</xliff:g>, colonne <xliff:g id="NUMBER_1">%2$s</xliff:g> dans <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Déplacer vers la position <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Déplacer vers la position <xliff:g id="NUMBER">%1$s</xliff:g> dans les favoris"</string>
     <string name="item_moved" msgid="4606538322571412879">"Élément déplacé"</string>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index 013011a..256c4d3 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Quitouse o elemento"</string>
     <string name="undo" msgid="4151576204245173321">"Desfacer"</string>
     <string name="action_move" msgid="4339390619886385032">"Mover elemento"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Mover á fila <xliff:g id="NUMBER_0">%1$s</xliff:g> columna <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Mover á fila <xliff:g id="NUMBER_0">%1$s</xliff:g>, columna <xliff:g id="NUMBER_1">%2$s</xliff:g> de <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Mover á posición <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Mover á posición dos favoritos <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Moveuse o elemento"</string>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 2d504e3..f1e1996 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"આઇટમ કાઢી નાખી"</string>
     <string name="undo" msgid="4151576204245173321">"રદ કરો"</string>
     <string name="action_move" msgid="4339390619886385032">"આઇટમ ખસેડો"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"<xliff:g id="NUMBER_0">%1$s</xliff:g> પંક્તિ <xliff:g id="NUMBER_1">%2$s</xliff:g> કૉલમ પર ખસેડો"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g>માં પંક્તિ <xliff:g id="NUMBER_0">%1$s</xliff:g> કૉલમ <xliff:g id="NUMBER_1">%2$s</xliff:g> પર આ આઇટમને ખસેડો"</string>
     <string name="move_to_position" msgid="6750008980455459790">"<xliff:g id="NUMBER">%1$s</xliff:g> સ્થિતિ પર ખસેડો"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"મનપસંદ સ્થિતિ <xliff:g id="NUMBER">%1$s</xliff:g> પર ખસેડો"</string>
     <string name="item_moved" msgid="4606538322571412879">"આઇટમ ખસેડી"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index de6ad1c..1b05040 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"आइटम हटाया गया"</string>
     <string name="undo" msgid="4151576204245173321">"पहले जैसा करें"</string>
     <string name="action_move" msgid="4339390619886385032">"आइटम ले जाएं"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"पंक्ति <xliff:g id="NUMBER_0">%1$s</xliff:g> स्तंभ <xliff:g id="NUMBER_1">%2$s</xliff:g> पर ले जाएं"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g> में, पंक्ति <xliff:g id="NUMBER_0">%1$s</xliff:g> कॉलम <xliff:g id="NUMBER_1">%2$s</xliff:g> पर जाएं"</string>
     <string name="move_to_position" msgid="6750008980455459790">"<xliff:g id="NUMBER">%1$s</xliff:g> स्थिति पर ले जाएं"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"<xliff:g id="NUMBER">%1$s</xliff:g> की पसंदीदा स्थिति पर ले जाएं"</string>
     <string name="item_moved" msgid="4606538322571412879">"आइटम ले जाया गया"</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 0e58e53..4a0fbe8 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Stavka je uklonjena"</string>
     <string name="undo" msgid="4151576204245173321">"Poništi"</string>
     <string name="action_move" msgid="4339390619886385032">"Premještanje stavke"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Premještanje u redak <xliff:g id="NUMBER_0">%1$s</xliff:g>, stupac <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Premjesti u redak <xliff:g id="NUMBER_0">%1$s</xliff:g> stupac <xliff:g id="NUMBER_1">%2$s</xliff:g> na <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Premještanje na položaj <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Premještanje na položaj favorita <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Stavka premještena"</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 30095d7..c1e527d 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Elem eltávolítva"</string>
     <string name="undo" msgid="4151576204245173321">"Mégse"</string>
     <string name="action_move" msgid="4339390619886385032">"Elem mozgatása"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Áthelyezés ide: <xliff:g id="NUMBER_0">%1$s</xliff:g>. sor, <xliff:g id="NUMBER_1">%2$s</xliff:g>. oszlop"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Áthelyezés a(z) <xliff:g id="NUMBER_0">%1$s</xliff:g>. sorba és a(z) <xliff:g id="NUMBER_1">%2$s</xliff:g>. oszlopba itt: <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Áthelyezés a(z) <xliff:g id="NUMBER">%1$s</xliff:g>. pozícióba"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Áthelyezés a kedvencek <xliff:g id="NUMBER">%1$s</xliff:g>. pozíciójába"</string>
     <string name="item_moved" msgid="4606538322571412879">"Elem áthelyezve"</string>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index 3b9761f..f0d01f1 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Տարրը հեռացվեց"</string>
     <string name="undo" msgid="4151576204245173321">"Հետարկել"</string>
     <string name="action_move" msgid="4339390619886385032">"Տեղափոխել տարրը"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Տեղափոխել տող <xliff:g id="NUMBER_0">%1$s</xliff:g> սյունակ <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Տեղափոխել շարք <xliff:g id="NUMBER_0">%1$s</xliff:g>, սյունակ <xliff:g id="NUMBER_1">%2$s</xliff:g> (<xliff:g id="STRING">%3$s</xliff:g>)"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Տեղափոխել դիրք <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Տեղափոխել նախընտրած դիրք՝ <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Տարրը տեղափոխվեց"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 02f4860..1a97a45 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Item dihapus"</string>
     <string name="undo" msgid="4151576204245173321">"Urungkan"</string>
     <string name="action_move" msgid="4339390619886385032">"Pindahkan item"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Pindahkan ke baris <xliff:g id="NUMBER_0">%1$s</xliff:g> kolom <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Pindahkan ke baris <xliff:g id="NUMBER_0">%1$s</xliff:g> kolom <xliff:g id="NUMBER_1">%2$s</xliff:g> di <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"PIndahkan ke posisi <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Pindahkan ke posisi favorit <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Item dipindahkan"</string>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index 17cf2e4..a1959f8 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Atriði fjarlægt"</string>
     <string name="undo" msgid="4151576204245173321">"Afturkalla"</string>
     <string name="action_move" msgid="4339390619886385032">"Færa atriði"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Færa í línu <xliff:g id="NUMBER_0">%1$s</xliff:g>, dálk <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Færðu þig í línu <xliff:g id="NUMBER_0">%1$s</xliff:g>, dálk <xliff:g id="NUMBER_1">%2$s</xliff:g> í <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Færa í stöðu <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Færa í stöðu <xliff:g id="NUMBER">%1$s</xliff:g> á festisvæði"</string>
     <string name="item_moved" msgid="4606538322571412879">"Atriði fært"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index edff814..ff4fe4f 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -137,7 +137,8 @@
     <string name="item_removed" msgid="851119963877842327">"Elemento rimosso"</string>
     <string name="undo" msgid="4151576204245173321">"Annulla"</string>
     <string name="action_move" msgid="4339390619886385032">"Sposta elemento"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Sposta a riga <xliff:g id="NUMBER_0">%1$s</xliff:g>, colonna <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <!-- no translation found for move_to_empty_cell_description (5254852678218206889) -->
+    <skip />
     <string name="move_to_position" msgid="6750008980455459790">"Sposta nella posizione <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Sposta nella posizione <xliff:g id="NUMBER">%1$s</xliff:g> dei preferiti"</string>
     <string name="item_moved" msgid="4606538322571412879">"Elemento spostato"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 7de8f08..15e200f 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"הפריט הוסר"</string>
     <string name="undo" msgid="4151576204245173321">"ביטול"</string>
     <string name="action_move" msgid="4339390619886385032">"העברת הפריט"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"העברה אל שורה <xliff:g id="NUMBER_0">%1$s</xliff:g> עמודה <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"צריך לעבור לשורה <xliff:g id="NUMBER_0">%1$s</xliff:g> ולטור <xliff:g id="NUMBER_1">%2$s</xliff:g> ב-<xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"העברה אל מיקום <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"העברה אל מיקום <xliff:g id="NUMBER">%1$s</xliff:g> במועדפים"</string>
     <string name="item_moved" msgid="4606538322571412879">"הפריט הועבר"</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index d7cbb07..2fd3ab5 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"アイテムを削除しました"</string>
     <string name="undo" msgid="4151576204245173321">"元に戻す"</string>
     <string name="action_move" msgid="4339390619886385032">"アイテムを移動"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"行<xliff:g id="NUMBER_0">%1$s</xliff:g>、列<xliff:g id="NUMBER_1">%2$s</xliff:g>に移動"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g> の行 <xliff:g id="NUMBER_0">%1$s</xliff:g>、列 <xliff:g id="NUMBER_1">%2$s</xliff:g> に移動します"</string>
     <string name="move_to_position" msgid="6750008980455459790">"位置<xliff:g id="NUMBER">%1$s</xliff:g>に移動"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"お気に入りの位置<xliff:g id="NUMBER">%1$s</xliff:g>に移動"</string>
     <string name="item_moved" msgid="4606538322571412879">"アイテムを移動しました"</string>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index ba5be87..24f95dd 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"ერთეული წაიშალა"</string>
     <string name="undo" msgid="4151576204245173321">"მოქმედების გაუქმება"</string>
     <string name="action_move" msgid="4339390619886385032">"ერთეულის გადაადგილება"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"გადატანა რიგში <xliff:g id="NUMBER_0">%1$s</xliff:g> სვეტში <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"გადაიტანეთ მწკრივი #<xliff:g id="NUMBER_0">%1$s</xliff:g> სვეტი #<xliff:g id="NUMBER_1">%2$s</xliff:g> <xliff:g id="STRING">%3$s</xliff:g>-ში"</string>
     <string name="move_to_position" msgid="6750008980455459790">"გადატანა <xliff:g id="NUMBER">%1$s</xliff:g> პოზიციაზე"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"გადატანა რჩეულთა პოზიციაზე <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"ერთეული გადაადგილდა"</string>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index 83b5a66..8ed7ec5 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Элемент жойылды"</string>
     <string name="undo" msgid="4151576204245173321">"Қайтару"</string>
     <string name="action_move" msgid="4339390619886385032">"Элементті жылжыту"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"<xliff:g id="NUMBER_0">%1$s</xliff:g>-жол, <xliff:g id="NUMBER_1">%2$s</xliff:g>-бағанға жылжыту"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g> экранында <xliff:g id="NUMBER_0">%1$s</xliff:g> жолын, <xliff:g id="NUMBER_1">%2$s</xliff:g> бағанын жылжытыңыз."</string>
     <string name="move_to_position" msgid="6750008980455459790">"<xliff:g id="NUMBER">%1$s</xliff:g>-орынға жылжыту"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"<xliff:g id="NUMBER">%1$s</xliff:g> нөмірлі таңдаулы орынға жылжыту"</string>
     <string name="item_moved" msgid="4606538322571412879">"Элемент жылжытылды"</string>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index 73ec4df..306449c 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"បានដកធាតុចេញ"</string>
     <string name="undo" msgid="4151576204245173321">"ត្រឡប់វិញ"</string>
     <string name="action_move" msgid="4339390619886385032">"ផ្លាស់ទីធាតុ"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"ផ្លាស់ទីទៅជួរដេកទី <xliff:g id="NUMBER_0">%1$s</xliff:g> ជួរឈរទី <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"ផ្លាស់ទៅជួរដេក <xliff:g id="NUMBER_0">%1$s</xliff:g> ជួរឈរ <xliff:g id="NUMBER_1">%2$s</xliff:g> ក្នុង <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"ផ្លាស់ទីទៅទីតាំង <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"ផ្លាស់ទីទៅការចូលចិត្តទីតាំងទី <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"បានផ្លាស់ទីធាតុ"</string>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 1e6f39e..a9b89b5 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"ಐಟಂ ತೆಗೆದುಹಾಕಲಾಗಿದೆ"</string>
     <string name="undo" msgid="4151576204245173321">"ರದ್ದುಮಾಡಿ"</string>
     <string name="action_move" msgid="4339390619886385032">"ಐಟಂ ಸರಿಸಿ"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"<xliff:g id="NUMBER_0">%1$s</xliff:g> ಸಾಲು <xliff:g id="NUMBER_1">%2$s</xliff:g> ಕಾಲಮ್‌ಗೆ ಸರಿಸಿ"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g> ನಲ್ಲಿ ಸಾಲು <xliff:g id="NUMBER_0">%1$s</xliff:g> ಅನ್ನು ಕಾಲಮ್ <xliff:g id="NUMBER_1">%2$s</xliff:g> ಗೆ ಸರಿಸಿ"</string>
     <string name="move_to_position" msgid="6750008980455459790">"<xliff:g id="NUMBER">%1$s</xliff:g> ಸ್ಥಾನಕ್ಕೆ ಸರಿಸಿ"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"ಮೆಚ್ಚಿನ <xliff:g id="NUMBER">%1$s</xliff:g> ಸ್ಥಾನಕ್ಕೆ ಸರಿಸಿ"</string>
     <string name="item_moved" msgid="4606538322571412879">"ಐಟಂ ಸರಿಸಲಾಗಿದೆ"</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index d5ec664..0935aa1 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"항목 삭제됨"</string>
     <string name="undo" msgid="4151576204245173321">"실행취소"</string>
     <string name="action_move" msgid="4339390619886385032">"항목 이동"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"<xliff:g id="NUMBER_0">%1$s</xliff:g>행 <xliff:g id="NUMBER_1">%2$s</xliff:g>열로 이동"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g>의 <xliff:g id="NUMBER_0">%1$s</xliff:g>행 <xliff:g id="NUMBER_1">%2$s</xliff:g>열로 이동"</string>
     <string name="move_to_position" msgid="6750008980455459790">"<xliff:g id="NUMBER">%1$s</xliff:g>번 위치로 이동"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"즐겨찾는 <xliff:g id="NUMBER">%1$s</xliff:g>번 위치로 이동"</string>
     <string name="item_moved" msgid="4606538322571412879">"항목을 이동했습니다."</string>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index ea4a7d2..755e274 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Жоюлду"</string>
     <string name="undo" msgid="4151576204245173321">"Кайтаруу"</string>
     <string name="action_move" msgid="4339390619886385032">"Муну жылдыруу"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"<xliff:g id="NUMBER_0">%1$s</xliff:g> катарга <xliff:g id="NUMBER_1">%2$s</xliff:g> тилкеге жылдыруу"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g> ичиндеги <xliff:g id="NUMBER_1">%2$s</xliff:g>-тилкенин <xliff:g id="NUMBER_0">%1$s</xliff:g>-cабына жылдыруу"</string>
     <string name="move_to_position" msgid="6750008980455459790">"<xliff:g id="NUMBER">%1$s</xliff:g> орунга жылдыруу"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Тандалмаларга <xliff:g id="NUMBER">%1$s</xliff:g> жылдыруу"</string>
     <string name="item_moved" msgid="4606538322571412879">"Нерсе жылдырылды"</string>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index 7291cf2..9e75492 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"ເອົາ​ລາຍ​ການ​ອອກ​ໄປ​ແລ້ວ"</string>
     <string name="undo" msgid="4151576204245173321">"ຍົກເລີກ"</string>
     <string name="action_move" msgid="4339390619886385032">"ຍ້າຍ​ລາຍ​ການ"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"ຍ້າຍ​ໄປ​ໃສ່​ແຖວ <xliff:g id="NUMBER_0">%1$s</xliff:g> ຖັນ <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"ຍ້າຍໄປແຖວ <xliff:g id="NUMBER_0">%1$s</xliff:g> ຖັນ <xliff:g id="NUMBER_1">%2$s</xliff:g> ໃນ <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"ຍ້າຍ​ໄປ​ໃສ່​ຕຳ​ແໜ່ງ <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"ຍ້າຍ​ໄປ​ໃສ່​ຕຳ​ແໜ່ງ​ທີ່​ມັກ <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"ຍ້າຍ​ລາຍ​ການ​ແລ້ວ"</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index ddff9d4..bbe0cfa 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Elementas perkeltas"</string>
     <string name="undo" msgid="4151576204245173321">"Anuliuoti"</string>
     <string name="action_move" msgid="4339390619886385032">"Perkelti elementą"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Perkelti į <xliff:g id="NUMBER_0">%1$s</xliff:g> eilutę, <xliff:g id="NUMBER_1">%2$s</xliff:g> stulpelį"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Perkelti į <xliff:g id="NUMBER_0">%1$s</xliff:g> eilutės <xliff:g id="NUMBER_1">%2$s</xliff:g> stulpelį, esantį <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Perkelti į <xliff:g id="NUMBER">%1$s</xliff:g> poziciją"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Perkelti į <xliff:g id="NUMBER">%1$s</xliff:g> mėgstamiausių poziciją"</string>
     <string name="item_moved" msgid="4606538322571412879">"Elementas perkeltas"</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index a5b7c66..b5836ef 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Vienums noņemts"</string>
     <string name="undo" msgid="4151576204245173321">"Atsaukt"</string>
     <string name="action_move" msgid="4339390619886385032">"Pārvietot vienumu"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Pārvietot uz <xliff:g id="NUMBER_0">%1$s</xliff:g>. rindu, <xliff:g id="NUMBER_1">%2$s</xliff:g>. kolonnu"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Pārvietot uz rindu numur <xliff:g id="NUMBER_0">%1$s</xliff:g>, kolonnu numur <xliff:g id="NUMBER_1">%2$s</xliff:g> šajā ekrānā: <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Pārvietot uz <xliff:g id="NUMBER">%1$s</xliff:g>. pozīciju"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Pārvietot uz <xliff:g id="NUMBER">%1$s</xliff:g>. izlases pozīciju"</string>
     <string name="item_moved" msgid="4606538322571412879">"Vienums pārvietots"</string>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index 32c3fc0..d452eba 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Ставката е отстранета"</string>
     <string name="undo" msgid="4151576204245173321">"Врати"</string>
     <string name="action_move" msgid="4339390619886385032">"Премести ја ставката"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Премести во ред <xliff:g id="NUMBER_0">%1$s</xliff:g> колона <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Преместете во редица <xliff:g id="NUMBER_0">%1$s</xliff:g>, колона <xliff:g id="NUMBER_1">%2$s</xliff:g> во <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Премести на место <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Премести на место <xliff:g id="NUMBER">%1$s</xliff:g> во омилени"</string>
     <string name="item_moved" msgid="4606538322571412879">"Ставката е преместена"</string>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index d25f9dd..df532c0 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"ഇനം നീക്കംചെയ്‌തു"</string>
     <string name="undo" msgid="4151576204245173321">"പഴയപടിയാക്കുക"</string>
     <string name="action_move" msgid="4339390619886385032">"ഇനം നീക്കുക"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"വരി <xliff:g id="NUMBER_0">%1$s</xliff:g> നിര <xliff:g id="NUMBER_1">%2$s</xliff:g>-ലേക്ക് നീക്കുക"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g> എന്നതിലെ <xliff:g id="NUMBER_1">%2$s</xliff:g>-ാം കോളത്തിലെ <xliff:g id="NUMBER_0">%1$s</xliff:g>-ാം വരിയിലേക്ക് നീക്കുക"</string>
     <string name="move_to_position" msgid="6750008980455459790">"<xliff:g id="NUMBER">%1$s</xliff:g>-ലേക്ക് നീക്കുക"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"ഇഷ്‌ടമുള്ള <xliff:g id="NUMBER">%1$s</xliff:g> സ്ഥാനത്തേക്ക് നീക്കുക"</string>
     <string name="item_moved" msgid="4606538322571412879">"ഇനം നീക്കി"</string>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index 0723c25..c71aaff 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Зүйлийг устгалаа"</string>
     <string name="undo" msgid="4151576204245173321">"Болих"</string>
     <string name="action_move" msgid="4339390619886385032">"Зөөх"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"<xliff:g id="NUMBER_0">%1$s</xliff:g> мөр <xliff:g id="NUMBER_1">%2$s</xliff:g> баганад зөөх"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g> дахь <xliff:g id="NUMBER_0">%1$s</xliff:g>-р мөр, <xliff:g id="NUMBER_1">%2$s</xliff:g>-р багана руу зөөх"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Байршил <xliff:g id="NUMBER">%1$s</xliff:g>-д зөөх"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Дуртай байршил болох <xliff:g id="NUMBER">%1$s</xliff:g>-д зөөх"</string>
     <string name="item_moved" msgid="4606538322571412879">"Зөөвөрлөсөн зүйл"</string>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index 06c1da7..e871073 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"आयटम काढला"</string>
     <string name="undo" msgid="4151576204245173321">"पूर्ववत करा"</string>
     <string name="action_move" msgid="4339390619886385032">"आयटम हलवा"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"पंक्ति <xliff:g id="NUMBER_0">%1$s</xliff:g> स्तंभ <xliff:g id="NUMBER_1">%2$s</xliff:g> मध्ये हलवा"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g> मधील <xliff:g id="NUMBER_0">%1$s</xliff:g> पंक्ती <xliff:g id="NUMBER_1">%2$s</xliff:g> स्तंभ यावर हलवा"</string>
     <string name="move_to_position" msgid="6750008980455459790">"<xliff:g id="NUMBER">%1$s</xliff:g> स्थानावर हलवा"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"आवडत्या <xliff:g id="NUMBER">%1$s</xliff:g> स्थानावर हलवा"</string>
     <string name="item_moved" msgid="4606538322571412879">"आयटम हलविला"</string>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index d123d14..cbfb551 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Item dialih keluar"</string>
     <string name="undo" msgid="4151576204245173321">"Buat asal"</string>
     <string name="action_move" msgid="4339390619886385032">"Alihkan Item"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Alihkan ke baris <xliff:g id="NUMBER_0">%1$s</xliff:g> lajur <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Alihkan ke baris <xliff:g id="NUMBER_0">%1$s</xliff:g> lajur <xliff:g id="NUMBER_1">%2$s</xliff:g> dalam <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Alihkan ke kedudukan <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Alihkan ke kedudukan kegemaran <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Item dialihkan"</string>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index aef6169..984ec4c 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"ဖယ်ရှားပြီးပြီ"</string>
     <string name="undo" msgid="4151576204245173321">"နောက်ပြန်ရန်"</string>
     <string name="action_move" msgid="4339390619886385032">"၎င်းအား ရွှေ့ပါ"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"အတန်း <xliff:g id="NUMBER_0">%1$s</xliff:g> အတိုင် <xliff:g id="NUMBER_1">%2$s</xliff:g> သို့ ရွှေ့ပါ"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g> တွင် အတန်း <xliff:g id="NUMBER_0">%1$s</xliff:g> ကော်လံ <xliff:g id="NUMBER_1">%2$s</xliff:g> သို့ ရွှေ့နိုင်သည်"</string>
     <string name="move_to_position" msgid="6750008980455459790">"<xliff:g id="NUMBER">%1$s</xliff:g> သို့ နေရာရွှေ့ပါ"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"စိတ်ကြိုက်နေရာ <xliff:g id="NUMBER">%1$s</xliff:g> သို့ ရွှေ့ပါ"</string>
     <string name="item_moved" msgid="4606538322571412879">"၎င်းအားရွှေ့ပြီး"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index eba195c..b29ee77 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Elementet er fjernet"</string>
     <string name="undo" msgid="4151576204245173321">"Angre"</string>
     <string name="action_move" msgid="4339390619886385032">"Flytt elementet"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Flytt til rad <xliff:g id="NUMBER_0">%1$s</xliff:g>, kolonne <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Flytt til rad <xliff:g id="NUMBER_0">%1$s</xliff:g> kolonne <xliff:g id="NUMBER_1">%2$s</xliff:g> i <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Flytt til posisjon <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Flytt til posisjonen for favoritter <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Elementet er flyttet"</string>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 49d92ac..fcc1ece 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -92,8 +92,8 @@
     <string name="disabled_app_label" msgid="6673129024321402780">"असक्षम पारिएको <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
     <string name="dotted_app_label" msgid="1865617679843363410">"{count,plural, =1{{app_name} सँग सम्बन्धित # सूचना छ}other{{app_name} सँग सम्बन्धित # वटा सूचना छन्}}"</string>
     <string name="default_scroll_format" msgid="7475544710230993317">"पृष्ठ %2$d को %1$d"</string>
-    <string name="workspace_scroll_format" msgid="8458889198184077399">"गृह स्क्रिन %1$d को %2$d"</string>
-    <string name="workspace_new_page" msgid="257366611030256142">"नयाँ गृह स्क्रिन पृष्ठ"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"होम स्क्रिन %1$d को %2$d"</string>
+    <string name="workspace_new_page" msgid="257366611030256142">"नयाँ होम स्क्रिन पृष्ठ"</string>
     <string name="folder_opened" msgid="94695026776264709">"फोल्डर खुल्यो <xliff:g id="WIDTH">%1$d</xliff:g> बाट <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"फोल्डरलाई बन्द गर्न ट्याप गर्नुहोस्"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"पुनःनामाकरणलाई सुरक्षित गर्न ट्याप गर्नुहोस्"</string>
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"वस्तु हटाइयो"</string>
     <string name="undo" msgid="4151576204245173321">"अन्डू गर्नुहोस्"</string>
     <string name="action_move" msgid="4339390619886385032">"वस्तु सार्नुहोस्"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"पङ्क्ति <xliff:g id="NUMBER_0">%1$s</xliff:g> स्तम्भ <xliff:g id="NUMBER_1">%2$s</xliff:g> मा सार्नुहोस्"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"यो वस्तु सारेर <xliff:g id="STRING">%3$s</xliff:g> मा रहेको रो <xliff:g id="NUMBER_0">%1$s</xliff:g> कोलम <xliff:g id="NUMBER_1">%2$s</xliff:g> मा लैजानुहोस्"</string>
     <string name="move_to_position" msgid="6750008980455459790">"स्थिति <xliff:g id="NUMBER">%1$s</xliff:g> मा सार्नुहोस्"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"मन पर्ने स्थिति <xliff:g id="NUMBER">%1$s</xliff:g> मा सार्नुहोस्"</string>
     <string name="item_moved" msgid="4606538322571412879">"वस्तु सारियो"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index e19b361..bd41952 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Item verwijderd"</string>
     <string name="undo" msgid="4151576204245173321">"Ongedaan maken"</string>
     <string name="action_move" msgid="4339390619886385032">"Item verplaatsen"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Verplaatsen naar rij <xliff:g id="NUMBER_0">%1$s</xliff:g>, kolom <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Verplaatsen naar rij <xliff:g id="NUMBER_0">%1$s</xliff:g> kolom <xliff:g id="NUMBER_1">%2$s</xliff:g> in <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Verplaatsen naar positie <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Verplaatsen naar positie <xliff:g id="NUMBER">%1$s</xliff:g> voor favorieten"</string>
     <string name="item_moved" msgid="4606538322571412879">"Item verplaatst"</string>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 2506200..5a6cdd3 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"ଆଇଟମକୁ କାଢ଼ି ଦିଆଯାଇଛି"</string>
     <string name="undo" msgid="4151576204245173321">"ପୂର୍ବବତ୍‍"</string>
     <string name="action_move" msgid="4339390619886385032">"ଆଇଟମ୍‌ ଘୁଞ୍ଚାନ୍ତୁ"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"ଧାଡ଼ି <xliff:g id="NUMBER_0">%1$s</xliff:g> ସ୍ତମ୍ଭ <xliff:g id="NUMBER_1">%2$s</xliff:g>କୁ ନିଅନ୍ତୁ"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g>ରେ ଧାଡି <xliff:g id="NUMBER_0">%1$s</xliff:g> ସ୍ତମ୍ଭ <xliff:g id="NUMBER_1">%2$s</xliff:g>କୁ ମୁଭ କରନ୍ତୁ"</string>
     <string name="move_to_position" msgid="6750008980455459790">"<xliff:g id="NUMBER">%1$s</xliff:g> ସ୍ଥିତିକୁ ନିଅନ୍ତୁ"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"ପସନ୍ଦର ସ୍ଥିତି <xliff:g id="NUMBER">%1$s</xliff:g>କୁ ନିଅନ୍ତୁ"</string>
     <string name="item_moved" msgid="4606538322571412879">"ଆଇଟମ୍‌ ଘୁଞ୍ଚେଇ ଦିଆଗଲା"</string>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 3df6886..01fbee0 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"ਆਈਟਮ ਹਟਾਈ ਗਈ"</string>
     <string name="undo" msgid="4151576204245173321">"ਅਣਕੀਤਾ ਕਰੋ"</string>
     <string name="action_move" msgid="4339390619886385032">"ਆਈਟਮ ਨੂੰ ਮੂਵ ਕਰੋ"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"ਕਤਾਰ <xliff:g id="NUMBER_0">%1$s</xliff:g> ਕਾਲਮ <xliff:g id="NUMBER_1">%2$s</xliff:g> ਵਿੱਚ ਮੂਵ ਕਰੋ"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g> ਵਿੱਚ ਕਤਾਰ <xliff:g id="NUMBER_0">%1$s</xliff:g> ਦੇ ਕਾਲਮ <xliff:g id="NUMBER_1">%2$s</xliff:g> \'ਤੇ ਜਾਓ"</string>
     <string name="move_to_position" msgid="6750008980455459790">"ਸਥਿਤੀ <xliff:g id="NUMBER">%1$s</xliff:g> ਵਿੱਚ ਮੂਵ ਕਰੋ"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"ਮਨਪਸੰਦ ਸਥਿਤੀ <xliff:g id="NUMBER">%1$s</xliff:g> ਵਿੱਚ ਮੂਵ ਕਰੋ"</string>
     <string name="item_moved" msgid="4606538322571412879">"ਆਈਟਮ ਮੂਵ ਕੀਤੀ ਗਈ"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 869e91b..4b46f0b 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Element został usunięty"</string>
     <string name="undo" msgid="4151576204245173321">"Cofnij"</string>
     <string name="action_move" msgid="4339390619886385032">"Przenieś element"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Przenieś do wiersza <xliff:g id="NUMBER_0">%1$s</xliff:g> w kolumnie <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Przenieś do wiersza <xliff:g id="NUMBER_0">%1$s</xliff:g> w kolumnie <xliff:g id="NUMBER_1">%2$s</xliff:g>, komórka: <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Przenieś do pozycji <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Przenieś do pozycji ulubionych: <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Element został przeniesiony"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 1512754..dbfa0cb 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Item removido"</string>
     <string name="undo" msgid="4151576204245173321">"Anular"</string>
     <string name="action_move" msgid="4339390619886385032">"Mover item"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Mover para a linha <xliff:g id="NUMBER_0">%1$s</xliff:g>, coluna <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Mova para a linha <xliff:g id="NUMBER_0">%1$s</xliff:g>, coluna <xliff:g id="NUMBER_1">%2$s</xliff:g> em <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Mover para a posição <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Mover para a posição <xliff:g id="NUMBER">%1$s</xliff:g> dos favoritos"</string>
     <string name="item_moved" msgid="4606538322571412879">"Item movido"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 2dcb98f..3a5ed5b 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Item removido"</string>
     <string name="undo" msgid="4151576204245173321">"Desfazer"</string>
     <string name="action_move" msgid="4339390619886385032">"Mover item"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Mover para a linha <xliff:g id="NUMBER_0">%1$s</xliff:g>, coluna <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Mover para a linha <xliff:g id="NUMBER_0">%1$s</xliff:g>, coluna <xliff:g id="NUMBER_1">%2$s</xliff:g> na <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Mover para a posição <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Mover para a posição <xliff:g id="NUMBER">%1$s</xliff:g> dos favoritos"</string>
     <string name="item_moved" msgid="4606538322571412879">"Item movido"</string>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index ee9f492..1b3d2b1 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Element eliminat"</string>
     <string name="undo" msgid="4151576204245173321">"Anulați"</string>
     <string name="action_move" msgid="4339390619886385032">"Mutați elementul"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Mutați pe rândul <xliff:g id="NUMBER_0">%1$s</xliff:g>, coloana <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Mutați în rândul <xliff:g id="NUMBER_0">%1$s</xliff:g> coloana <xliff:g id="NUMBER_1">%2$s</xliff:g> din <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Mutați pe poziția <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Mutați în preferate, pe poziția <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Element mutat"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 9733ac6..0ad12bd 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Объект убран."</string>
     <string name="undo" msgid="4151576204245173321">"Отменить"</string>
     <string name="action_move" msgid="4339390619886385032">"Переместить элемент"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Переместить в ячейку <xliff:g id="NUMBER_0">%1$s</xliff:g> <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Переместите в строку <xliff:g id="NUMBER_0">%1$s</xliff:g> столбца <xliff:g id="NUMBER_1">%2$s</xliff:g> на экране \"<xliff:g id="STRING">%3$s</xliff:g>\""</string>
     <string name="move_to_position" msgid="6750008980455459790">"Переместить в позицию <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Переместить в Избранное (<xliff:g id="NUMBER">%1$s</xliff:g>)"</string>
     <string name="item_moved" msgid="4606538322571412879">"Элемент перемещен."</string>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index e160e81..cf3dc64 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"අයිතමය ඉවත් කරන ලදි"</string>
     <string name="undo" msgid="4151576204245173321">"අස් කරන්න"</string>
     <string name="action_move" msgid="4339390619886385032">"අයිතමය ගෙනයන්න"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"පේළිය <xliff:g id="NUMBER_0">%1$s</xliff:g> තීරුව <xliff:g id="NUMBER_1">%2$s</xliff:g> වෙත ගෙන යන්න"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g> තුළ <xliff:g id="NUMBER_0">%1$s</xliff:g> තීරුවේ<xliff:g id="NUMBER_1">%2$s</xliff:g> පේළියට යන්න"</string>
     <string name="move_to_position" msgid="6750008980455459790">"<xliff:g id="NUMBER">%1$s</xliff:g> ස්ථානය වෙත ගෙන යන්න"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"ප්‍රියතම ස්ථානය <xliff:g id="NUMBER">%1$s</xliff:g> වෙත ගෙන යන්න"</string>
     <string name="item_moved" msgid="4606538322571412879">"අයිතමය ගෙන යන ලදි"</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 8582baf..e3bcc52 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Položka bola odstránená"</string>
     <string name="undo" msgid="4151576204245173321">"Späť"</string>
     <string name="action_move" msgid="4339390619886385032">"Presunúť položku"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Presunúť do stĺpca <xliff:g id="NUMBER_1">%2$s</xliff:g> v riadku <xliff:g id="NUMBER_0">%1$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Premiestnite položku do <xliff:g id="NUMBER_0">%1$s</xliff:g>. riadka <xliff:g id="NUMBER_1">%2$s</xliff:g>. stĺpca na obrazovke <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Presunúť na <xliff:g id="NUMBER">%1$s</xliff:g>. miesto"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Presunúť na <xliff:g id="NUMBER">%1$s</xliff:g>. miesto v obľúbených položkách"</string>
     <string name="item_moved" msgid="4606538322571412879">"Položka bola presunutá"</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 0762cf7..5f952cf 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Element je bil odstranjen."</string>
     <string name="undo" msgid="4151576204245173321">"Razveljavi"</string>
     <string name="action_move" msgid="4339390619886385032">"Premik elementa"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Premik v <xliff:g id="NUMBER_0">%1$s</xliff:g>. vrstico <xliff:g id="NUMBER_1">%2$s</xliff:g>. stolpca"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Premik v vrstico <xliff:g id="NUMBER_0">%1$s</xliff:g> v stolpcu <xliff:g id="NUMBER_1">%2$s</xliff:g> na »<xliff:g id="STRING">%3$s</xliff:g>«"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Premk na mesto št. <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Premik na mesto priljubljenih št. <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Element je premaknjen"</string>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index 821bd50..9f29a87 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Artikulli u hoq"</string>
     <string name="undo" msgid="4151576204245173321">"Zhbëj"</string>
     <string name="action_move" msgid="4339390619886385032">"Zhvendose artikullin"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Zhvendos te rreshti <xliff:g id="NUMBER_0">%1$s</xliff:g>, kolona <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Kalo te rreshti <xliff:g id="NUMBER_0">%1$s</xliff:g> kolona <xliff:g id="NUMBER_1">%2$s</xliff:g> në <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Zhvendos te pozicioni <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Zhvendos te pozicioni <xliff:g id="NUMBER">%1$s</xliff:g> i preferencave"</string>
     <string name="item_moved" msgid="4606538322571412879">"Artikulli u zhvendos"</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 85cc281..948d9b6 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Ставка је уклоњена"</string>
     <string name="undo" msgid="4151576204245173321">"Опозови"</string>
     <string name="action_move" msgid="4339390619886385032">"Премести ставку"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Премести у ред <xliff:g id="NUMBER_0">%1$s</xliff:g> и колону <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Преместите у ред <xliff:g id="NUMBER_0">%1$s</xliff:g> колону <xliff:g id="NUMBER_1">%2$s</xliff:g> на <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Премести на <xliff:g id="NUMBER">%1$s</xliff:g>. позицију"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Премести на <xliff:g id="NUMBER">%1$s</xliff:g>. позицију у омиљеним"</string>
     <string name="item_moved" msgid="4606538322571412879">"Ставка је премештена"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 7525eec..c35adb0 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Objektet har tagits bort"</string>
     <string name="undo" msgid="4151576204245173321">"Ångra"</string>
     <string name="action_move" msgid="4339390619886385032">"Flytta objekt"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Flytta till rad <xliff:g id="NUMBER_0">%1$s</xliff:g>, kolumn <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Flytta till rad <xliff:g id="NUMBER_0">%1$s</xliff:g> kolumn <xliff:g id="NUMBER_1">%2$s</xliff:g> i <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Flytta till plats <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Flytta till favoritplats <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Objektet har flyttats"</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 3ae5dcf..c233cc4 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Kipengee kimeondolewa"</string>
     <string name="undo" msgid="4151576204245173321">"Tendua"</string>
     <string name="action_move" msgid="4339390619886385032">"Hamisha kipengee"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Hamishia safu mlalo <xliff:g id="NUMBER_0">%1$s</xliff:g> safu wima <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Hamishia safu mlalo <xliff:g id="NUMBER_0">%1$s</xliff:g> safu wima <xliff:g id="NUMBER_1">%2$s</xliff:g> kwenye <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Hamishia nafasi ya <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Hamishia nafasi inayopendwa ya <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Kipengee kimesogezwa"</string>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 0547fc2..bfec460 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"அகற்றப்பட்டது"</string>
     <string name="undo" msgid="4151576204245173321">"செயல்தவிர்"</string>
     <string name="action_move" msgid="4339390619886385032">"நகர்த்து"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"<xliff:g id="NUMBER_0">%1$s</xliff:g> வரிசை, <xliff:g id="NUMBER_1">%2$s</xliff:g> நெடுவரிசைக்கு நகர்த்து"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g> என்பதில் <xliff:g id="NUMBER_0">%1$s</xliff:g>வது வரிசை <xliff:g id="NUMBER_1">%2$s</xliff:g>வது நெடுவரிசைக்கு நகர்த்தப்படும்"</string>
     <string name="move_to_position" msgid="6750008980455459790">"நிலை <xliff:g id="NUMBER">%1$s</xliff:g>க்கு நகர்த்து"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"விரும்பும் நிலை <xliff:g id="NUMBER">%1$s</xliff:g>க்கு நகர்த்து"</string>
     <string name="item_moved" msgid="4606538322571412879">"உருப்படி நகர்த்தப்பட்டது"</string>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 663e44f..8b42a8f 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"ఐటెమ్ తీసివేయబడింది"</string>
     <string name="undo" msgid="4151576204245173321">"చర్య రద్దు"</string>
     <string name="action_move" msgid="4339390619886385032">"అంశాన్ని తరలించు"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"అడ్డు వరుస <xliff:g id="NUMBER_0">%1$s</xliff:g> నిలువు వరుస <xliff:g id="NUMBER_1">%2$s</xliff:g>కి తరలించు"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g> లో <xliff:g id="NUMBER_0">%1$s</xliff:g> అడ్డు వరుస <xliff:g id="NUMBER_1">%2$s</xliff:g>నిలువు వరుసకు తరలించండి"</string>
     <string name="move_to_position" msgid="6750008980455459790">"<xliff:g id="NUMBER">%1$s</xliff:g>వ స్థానానికి తరలించు"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"ఇష్టమైనవిలో <xliff:g id="NUMBER">%1$s</xliff:g>వ స్థానానికి తరలించు"</string>
     <string name="item_moved" msgid="4606538322571412879">"అంశం తరలించబడింది"</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 07e5796..f633b5f 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"นำรายการออกแล้ว"</string>
     <string name="undo" msgid="4151576204245173321">"เลิกทำ"</string>
     <string name="action_move" msgid="4339390619886385032">"ย้ายรายการ"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"ย้ายไปที่แถว <xliff:g id="NUMBER_0">%1$s</xliff:g> คอลัมน์ <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"ย้ายไปยังแถว <xliff:g id="NUMBER_0">%1$s</xliff:g> คอลัมน์ <xliff:g id="NUMBER_1">%2$s</xliff:g> ใน <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"ย้ายไปยังตำแหน่ง <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"ย้ายไปยังตำแหน่งที่ <xliff:g id="NUMBER">%1$s</xliff:g> ของรายการโปรด"</string>
     <string name="item_moved" msgid="4606538322571412879">"ย้ายรายการแล้ว"</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index f7158b4..de4f952 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Naalis na ang item"</string>
     <string name="undo" msgid="4151576204245173321">"I-undo"</string>
     <string name="action_move" msgid="4339390619886385032">"Ilipat ang item"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Ilipat sa row <xliff:g id="NUMBER_0">%1$s</xliff:g> column <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Pumunta sa row <xliff:g id="NUMBER_0">%1$s</xliff:g> column <xliff:g id="NUMBER_1">%2$s</xliff:g> sa <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Ilipat sa posisyon <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Ilipat sa posisyon <xliff:g id="NUMBER">%1$s</xliff:g> sa mga paborito"</string>
     <string name="item_moved" msgid="4606538322571412879">"Nalipat ang item"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index b12b9b1..f41284b 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Öğe silindi"</string>
     <string name="undo" msgid="4151576204245173321">"Geri al"</string>
     <string name="action_move" msgid="4339390619886385032">"Öğeyi taşı"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"<xliff:g id="NUMBER_0">%1$s</xliff:g>. satır <xliff:g id="NUMBER_1">%2$s</xliff:g>. sütuna taşı"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g> sayfasında <xliff:g id="NUMBER_0">%1$s</xliff:g>. satır <xliff:g id="NUMBER_1">%2$s</xliff:g>. sütuna taşı"</string>
     <string name="move_to_position" msgid="6750008980455459790">"<xliff:g id="NUMBER">%1$s</xliff:g>. sıraya taşı"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Favorilerde <xliff:g id="NUMBER">%1$s</xliff:g>. sıraya taşı"</string>
     <string name="item_moved" msgid="4606538322571412879">"Öğe taşındı"</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 2b43a4d..360950e 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Елемент вилучено"</string>
     <string name="undo" msgid="4151576204245173321">"Відмінити"</string>
     <string name="action_move" msgid="4339390619886385032">"Перемістити елемент"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Перемістити в рядок <xliff:g id="NUMBER_0">%1$s</xliff:g>, стовпець <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Перенести: <xliff:g id="STRING">%3$s</xliff:g>, рядок <xliff:g id="NUMBER_0">%1$s</xliff:g>, стовпець <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Перемістити на <xliff:g id="NUMBER">%1$s</xliff:g> місце"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Перемістити у вибране на <xliff:g id="NUMBER">%1$s</xliff:g> місце"</string>
     <string name="item_moved" msgid="4606538322571412879">"Елемент переміщено"</string>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index 70a853f..e68e43e 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"آئٹم ہٹا دیا گیا"</string>
     <string name="undo" msgid="4151576204245173321">"کالعدم کریں"</string>
     <string name="action_move" msgid="4339390619886385032">"آئٹم منتقل کریں"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"قطار <xliff:g id="NUMBER_0">%1$s</xliff:g> کالم <xliff:g id="NUMBER_1">%2$s</xliff:g> میں منتقل کریں"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g> میں قطار <xliff:g id="NUMBER_0">%1$s</xliff:g> کالم <xliff:g id="NUMBER_1">%2$s</xliff:g> میں منتقل کریں"</string>
     <string name="move_to_position" msgid="6750008980455459790">"پوزیشن <xliff:g id="NUMBER">%1$s</xliff:g> میں منتقل کریں"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"پسندیدہ پوزیشن <xliff:g id="NUMBER">%1$s</xliff:g> میں منتقل کریں"</string>
     <string name="item_moved" msgid="4606538322571412879">"آئٹم منتقل کر دیا گیا"</string>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index 57a446b..9c7ed56 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Element olib tashlandi"</string>
     <string name="undo" msgid="4151576204245173321">"Qaytarish"</string>
     <string name="action_move" msgid="4339390619886385032">"Obyektni ko‘chirib o‘tkazish"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"<xliff:g id="NUMBER_0">%1$s</xliff:g> <xliff:g id="NUMBER_1">%2$s</xliff:g> katakka olish"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g> ichidagi <xliff:g id="NUMBER_0">%1$s</xliff:g>-qator <xliff:g id="NUMBER_1">%2$s</xliff:g>-ustuniga oʻting"</string>
     <string name="move_to_position" msgid="6750008980455459790">"<xliff:g id="NUMBER">%1$s</xliff:g>-joyga olish"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Sevimlilarga olish (<xliff:g id="NUMBER">%1$s</xliff:g>)"</string>
     <string name="item_moved" msgid="4606538322571412879">"Element ko‘chirib o‘tkazildi"</string>
diff --git a/res/values-v31/colors.xml b/res/values-v31/colors.xml
index 7bbdbd1..7a75ddb 100644
--- a/res/values-v31/colors.xml
+++ b/res/values-v31/colors.xml
@@ -56,4 +56,9 @@
 
     <color name="workspace_accent_color_light">@android:color/system_accent1_100</color>
     <color name="workspace_accent_color_dark">@android:color/system_accent2_600</color>
+
+    <color name="preload_icon_accent_color_light">@android:color/system_accent1_600</color>
+    <color name="preload_icon_background_color_light">@android:color/system_accent2_200</color>
+    <color name="preload_icon_accent_color_dark">@android:color/system_accent1_300</color>
+    <color name="preload_icon_background_color_dark">@android:color/system_neutral2_700</color>
 </resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 2c81ae7..30167fa 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Đã xóa mục"</string>
     <string name="undo" msgid="4151576204245173321">"Hủy"</string>
     <string name="action_move" msgid="4339390619886385032">"Di chuyển mục"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Di chuyển đến hàng <xliff:g id="NUMBER_0">%1$s</xliff:g> cột <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Di chuyển đến hàng <xliff:g id="NUMBER_0">%1$s</xliff:g> cột <xliff:g id="NUMBER_1">%2$s</xliff:g> tại <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Di chuyển tới vị trí <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Di chuyển tới vị trí mục yêu thích <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Đã di chuyển mục"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 7c104e2..141d165 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"项目已移除"</string>
     <string name="undo" msgid="4151576204245173321">"撤消"</string>
     <string name="action_move" msgid="4339390619886385032">"移动项目"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"移至第 <xliff:g id="NUMBER_0">%1$s</xliff:g> 行第 <xliff:g id="NUMBER_1">%2$s</xliff:g> 列"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"移至<xliff:g id="STRING">%3$s</xliff:g>中的第 <xliff:g id="NUMBER_0">%1$s</xliff:g> 行第 <xliff:g id="NUMBER_1">%2$s</xliff:g> 列"</string>
     <string name="move_to_position" msgid="6750008980455459790">"移至第 <xliff:g id="NUMBER">%1$s</xliff:g> 个位置"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"移至收藏夹第 <xliff:g id="NUMBER">%1$s</xliff:g> 个位置"</string>
     <string name="item_moved" msgid="4606538322571412879">"已移动项目"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index ed288d6..2ff233b 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"項目已移除"</string>
     <string name="undo" msgid="4151576204245173321">"復原"</string>
     <string name="action_move" msgid="4339390619886385032">"移動項目"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"移動至第 <xliff:g id="NUMBER_0">%1$s</xliff:g> 行第 <xliff:g id="NUMBER_1">%2$s</xliff:g> 列"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"移去 <xliff:g id="STRING">%3$s</xliff:g> 嘅第 <xliff:g id="NUMBER_0">%1$s</xliff:g> 列,第 <xliff:g id="NUMBER_1">%2$s</xliff:g> 欄"</string>
     <string name="move_to_position" msgid="6750008980455459790">"移動至位置 <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"移動至喜愛的位置 <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"已移動項目"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 3690c3a..91cfd82 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"已移除項目"</string>
     <string name="undo" msgid="4151576204245173321">"復原"</string>
     <string name="action_move" msgid="4339390619886385032">"移動項目"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"移至第 <xliff:g id="NUMBER_0">%1$s</xliff:g> 列第 <xliff:g id="NUMBER_1">%2$s</xliff:g> 欄"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"移動至 <xliff:g id="STRING">%3$s</xliff:g> 的第 <xliff:g id="NUMBER_0">%1$s</xliff:g> 列,第 <xliff:g id="NUMBER_1">%2$s</xliff:g> 欄"</string>
     <string name="move_to_position" msgid="6750008980455459790">"移至位置 <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"移至收藏位置 <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"已移動項目"</string>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 26e7dcf..12e8568 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Into isusiwe"</string>
     <string name="undo" msgid="4151576204245173321">"Susa"</string>
     <string name="action_move" msgid="4339390619886385032">"Hambisa into"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Hambisa kurowu engu-<xliff:g id="NUMBER_0">%1$s</xliff:g> ikholomu engu-<xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Hamba emugqeni <xliff:g id="NUMBER_0">%1$s</xliff:g> ikholomu <xliff:g id="NUMBER_1">%2$s</xliff:g> ku-<xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Hambisa kusimo esingu-<xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Hambisa kusimo sezintandokazi esingu-<xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Into ihanjisiwe"</string>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 13f20c2..af8d8eb 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -53,6 +53,8 @@
     <attr name="workProfileOverlayTextColor" format="color" />
     <attr name="workspaceAccentColor" format="color" />
     <attr name="dropTargetHoverTextColor" format="color" />
+    <attr name="preloadIconAccentColor" format="color" />
+    <attr name="preloadIconBackgroundColor" format="color" />
 
     <attr name="allAppsButtonBgColor" format="color" />
     <attr name="allAppsButtonColor1" format="color" />
@@ -252,9 +254,10 @@
         if not specified -->
         <attr name="borderSpaceTwoPanelLandscapeVertical" format="float" />
 
-        <!-- These min cell values are only used if GridDisplayOption#isScalable is true -->
-        <!-- defaults to minCellHeight, if not specified -->
+        <!-- defaults to minCellHeight if not specified when GridDisplayOption#isScalable is true.
+         Must be defined when GridDisplayOption#isScalable is false. -->
         <attr name="allAppsCellHeight" format="float" />
+        <!-- These min cell values are only used if GridDisplayOption#isScalable is true -->
         <!-- defaults to minCellWidth, if not specified -->
         <attr name="allAppsCellWidth" format="float" />
         <!-- defaults to allAppsCellHeight, if not specified -->
@@ -326,6 +329,24 @@
         <!-- defaults to hotseatBorderSpace, if not specified -->
         <attr name="hotseatBorderSpaceTwoPanelPortrait" format="float" />
 
+        <!-- defaults to res.hotseat_bar_bottom_space_default, if not specified -->
+        <attr name="hotseatBarBottomSpace" format="float" />
+        <!-- defaults to hotseatBarBottomSpace, if not specified -->
+        <attr name="hotseatBarBottomSpaceLandscape" format="float" />
+        <!-- defaults to hotseatBarBottomSpace, if not specified -->
+        <attr name="hotseatBarBottomSpaceTwoPanelLandscape" format="float" />
+        <!-- defaults to hotseatBarBottomSpace, if not specified -->
+        <attr name="hotseatBarBottomSpaceTwoPanelPortrait" format="float" />
+
+        <!-- defaults to res.hotseat_qsb_space_default, if not specified -->
+        <attr name="hotseatQsbSpace" format="float" />
+        <!-- defaults to hotseatQsbSpace, if not specified -->
+        <attr name="hotseatQsbSpaceLandscape" format="float" />
+        <!-- defaults to hotseatQsbSpace, if not specified -->
+        <attr name="hotseatQsbSpaceTwoPanelLandscape" format="float" />
+        <!-- defaults to hotseatQsbSpace, if not specified -->
+        <attr name="hotseatQsbSpaceTwoPanelPortrait" format="float" />
+
         <attr name="iconImageSize" format="float" />
         <!-- defaults to iconImageSize, if not specified -->
         <attr name="iconSizeLandscape" format="float" />
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 2bc9239..309a1c5 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -85,4 +85,9 @@
     <color name="all_apps_button_color_2">#00677E</color>
     <color name="all_apps_button_color_3">#5F757E</color>
     <color name="all_apps_button_color_4">#005A6E</color>
+
+    <color name="preload_icon_accent_color_light">#00668B</color>
+    <color name="preload_icon_background_color_light">#B5CAD7</color>
+    <color name="preload_icon_accent_color_dark">#4BB6E8</color>
+    <color name="preload_icon_background_color_dark">#40484D</color>
 </resources>
diff --git a/res/values/config.xml b/res/values/config.xml
index 9aa1f03..3f94c34 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -130,6 +130,11 @@
     <item type="id" name="search_container_all_apps" />
     <item type="id" name="search_container_hotseat" />
 
+    <!-- Scalable Grid configuration -->
+    <!-- This is a float because it is converted to dp later in DeviceProfile -->
+    <dimen name="hotseat_bar_bottom_space_default">48</dimen>
+    <dimen name="hotseat_qsb_space_default">0</dimen>
+
     <!-- Recents -->
     <item type="id" name="overview_panel"/>
 
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 8403af4..5e33de8 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -32,10 +32,7 @@
     <dimen name="dynamic_grid_cell_padding_x">8dp</dimen>
 
     <!-- Hotseat -->
-    <dimen name="dynamic_grid_hotseat_top_padding">8dp</dimen>
-    <dimen name="dynamic_grid_hotseat_bottom_padding">2dp</dimen>
     <dimen name="dynamic_grid_hotseat_bottom_tall_padding">0dp</dimen>
-    <dimen name="inline_qsb_bottom_margin">0dp</dimen>
     <dimen name="spring_loaded_hotseat_top_margin">76dp</dimen>
 
     <!-- Qsb -->
@@ -44,13 +41,10 @@
       it is close to the bottom of the screen -->
     <item name="qsb_center_factor" format="float" type="dimen">0.325</item>
 
-    <!-- Extra bottom padding for non-tall devices. -->
-    <dimen name="dynamic_grid_hotseat_bottom_non_tall_padding">0dp</dimen>
-    <dimen name="dynamic_grid_hotseat_extra_vertical_size">34dp</dimen>
     <dimen name="dynamic_grid_hotseat_side_padding">0dp</dimen>
 
     <!-- Scalable Grid -->
-    <dimen name="scalable_grid_qsb_bottom_margin">42dp</dimen>
+    <dimen name="min_qsb_margin">8dp</dimen>
 
     <!-- Workspace page indicator -->
     <dimen name="workspace_page_indicator_height">24dp</dimen>
@@ -364,6 +358,7 @@
     <dimen name="taskbar_size">0dp</dimen>
     <dimen name="taskbar_stashed_size">0dp</dimen>
     <dimen name="qsb_widget_height">0dp</dimen>
+    <dimen name="qsb_shadow_height">0dp</dimen>
     <dimen name="taskbar_icon_size">44dp</dimen>
     <!-- Note that this applies to both sides of all icons, so visible space is double this. -->
     <dimen name="taskbar_icon_spacing">8dp</dimen>
@@ -391,8 +386,17 @@
     <dimen name="split_placeholder_inset">16dp</dimen>
     <dimen name="split_placeholder_icon_size">44dp</dimen>
     <dimen name="task_menu_width_grid">216dp</dimen>
-
-
+    <dimen name="split_instructions_radius">22dp</dimen>
+    <dimen name="split_instructions_elevation">1dp</dimen>
+    <dimen name="split_instructions_horizontal_padding">24dp</dimen>
+    <dimen name="split_instructions_vertical_padding">12dp</dimen>
+    <dimen name="split_instructions_bottom_margin_tablet_landscape">32dp</dimen>
+    <dimen name="split_instructions_bottom_margin_tablet_portrait">44dp</dimen>
+    <dimen name="split_instructions_bottom_margin_twopanels_landscape">33dp</dimen>
+    <dimen name="split_instructions_bottom_margin_twopanels_portrait">51dp</dimen>
+    <dimen name="split_instructions_bottom_margin_phone_landscape">24dp</dimen>
+    <dimen name="split_instructions_bottom_margin_phone_portrait">60dp</dimen>
+    
     <!-- Workspace grid visualization parameters -->
     <dimen name="grid_visualization_rounding_radius">28dp</dimen>
     <dimen name="grid_visualization_horizontal_cell_spacing">6dp</dimen>
diff --git a/res/values/id.xml b/res/values/id.xml
index af21b27..9fc0ff8 100644
--- a/res/values/id.xml
+++ b/res/values/id.xml
@@ -16,7 +16,6 @@
 -->
 <resources>
     <item type="id" name="apps_list_view_work" />
-    <item type="id" name="tag_widget_entry" />
     <item type="id" name="view_type_widgets_space" />
     <item type="id" name="view_type_widgets_list" />
     <item type="id" name="view_type_widgets_header" />
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 847e4a8..2addf50 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -344,7 +344,7 @@
     <string name="action_move">Move item</string>
 
     <!-- Accessibility description to move item to empty cell. -->
-    <string name="move_to_empty_cell">Move to row <xliff:g id="number" example="1">%1$s</xliff:g> column <xliff:g id="number" example="1">%2$s</xliff:g></string>
+    <string name="move_to_empty_cell_description">Move to row <xliff:g id="number" example="1">%1$s</xliff:g> column <xliff:g id="number" example="1">%2$s</xliff:g> in <xliff:g id="string" example="Home screen 2 of 4">%3$s</xliff:g></string>
 
     <!-- Accessibility description to move item inside a folder. -->
     <string name="move_to_position">Move to position <xliff:g id="number" example="1">%1$s</xliff:g></string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 2109510..65bba7b 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -65,6 +65,8 @@
         <item name="workspaceAccentColor">@color/workspace_accent_color_light</item>
         <item name="dropTargetHoverTextColor">@color/workspace_text_color_dark</item>
         <item name="overviewScrimColor">@color/overview_scrim</item>
+        <item name="preloadIconAccentColor">@color/preload_icon_accent_color_light</item>
+        <item name="preloadIconBackgroundColor">@color/preload_icon_background_color_light</item>
 
         <item name="android:windowTranslucentStatus">false</item>
         <item name="android:windowTranslucentNavigation">false</item>
@@ -123,6 +125,8 @@
         <item name="workProfileOverlayTextColor">@android:color/white</item>
         <item name="eduHalfSheetBGColor">#DD000000</item>
         <item name="overviewScrimColor">@color/overview_scrim_dark</item>
+        <item name="preloadIconAccentColor">@color/preload_icon_accent_color_dark</item>
+        <item name="preloadIconBackgroundColor">@color/preload_icon_background_color_dark</item>
     </style>
 
     <style name="LauncherTheme.Dark.DarkMainColor" parent="@style/LauncherTheme.Dark">
diff --git a/res/xml/device_profiles.xml b/res/xml/device_profiles.xml
index 0802552..5ee291b 100644
--- a/res/xml/device_profiles.xml
+++ b/res/xml/device_profiles.xml
@@ -199,6 +199,8 @@
             launcher:allAppsBorderSpaceLandscape="16"
             launcher:hotseatBorderSpace="58"
             launcher:hotseatBorderSpaceLandscape="50.4"
+            launcher:hotseatBarBottomSpace="76"
+            launcher:hotseatBarBottomSpaceLandscape="40"
             launcher:canBeDefault="true" />
 
     </grid-option>
diff --git a/res/xml/paddings_6x5.xml b/res/xml/paddings_6x5.xml
index a72f554..2f421b7 100644
--- a/res/xml/paddings_6x5.xml
+++ b/res/xml/paddings_6x5.xml
@@ -17,60 +17,29 @@
 
 <device-paddings xmlns:launcher="http://schemas.android.com/apk/res-auto" >
 
-    <!--  Some non default screen sizes  -->
     <device-padding
-        launcher:maxEmptySpace="30dp">
+        launcher:maxEmptySpace="100dp">
         <workspaceTopPadding
-            launcher:a="0.34"
+            launcher:a="0.31"
             launcher:b="0"/>
         <workspaceBottomPadding
-            launcher:a="0.26"
+            launcher:a="0.69"
             launcher:b="0"/>
         <hotseatBottomPadding
-            launcher:a="0.4"
-            launcher:b="0"/>
-    </device-padding>
-
-    <device-padding
-        launcher:maxEmptySpace="170dp">
-        <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="410dp">
-        <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"/>
+            launcher:b="0"/>
     </device-padding>
 
     <device-padding
         launcher:maxEmptySpace="9999dp">
         <workspaceTopPadding
-            launcher:a="0.40"
-            launcher:c="36dp"/>
+            launcher:a="0.48"
+            launcher:b="0"/>
         <workspaceBottomPadding
-            launcher:a="0.60"
-            launcher:c="36dp"/>
+            launcher:a="0.52"
+            launcher:b="0"/>
         <hotseatBottomPadding
             launcher:a="0"
-            launcher:b="36dp"/>
+            launcher:b="0"/>
     </device-padding>
 </device-paddings>
\ No newline at end of file
diff --git a/src/com/android/launcher3/Alarm.java b/src/com/android/launcher3/Alarm.java
index d5b434c..e4aebf6 100644
--- a/src/com/android/launcher3/Alarm.java
+++ b/src/com/android/launcher3/Alarm.java
@@ -30,6 +30,7 @@
     private Handler mHandler;
     private OnAlarmListener mAlarmListener;
     private boolean mAlarmPending = false;
+    private long mLastSetTimeout;
 
     public Alarm() {
         mHandler = new Handler();
@@ -46,6 +47,7 @@
         mAlarmPending = true;
         long oldTriggerTime = mAlarmTriggerTime;
         mAlarmTriggerTime = currentTime + millisecondsInFuture;
+        mLastSetTimeout = millisecondsInFuture;
 
         // If the previous alarm was set for a longer duration, cancel it.
         if (mWaitingForCallback && oldTriggerTime > mAlarmTriggerTime) {
@@ -84,4 +86,9 @@
     public boolean alarmPending() {
         return mAlarmPending;
     }
+
+    /** Returns the last value passed to {@link #setAlarm(long)} */
+    public long getLastSetTimeout() {
+        return mLastSetTimeout;
+    }
 }
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 73d3e33..d34f535 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -77,8 +77,8 @@
             new ArrayList<>();
 
     protected DeviceProfile mDeviceProfile;
-    protected StatsLogManager mStatsLogManager;
     protected SystemUiController mSystemUiController;
+    private StatsLogManager mStatsLogManager;
 
 
     public static final int ACTIVITY_STATE_STARTED = 1 << 0;
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 300e7bf..52dfcd4 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -1202,13 +1202,14 @@
             int row = cellY + 1;
             int col = workspace.mIsRtl ? mCountX - cellX : cellX + 1;
             int panelCount = workspace.getPanelCount();
+            int screenId = workspace.getIdForScreen(this);
+            int pageIndex = workspace.getPageIndexForScreenId(screenId);
             if (panelCount > 1) {
                 // Increment the column if the target is on the right side of a two panel home
-                int screenId = workspace.getIdForScreen(this);
-                int pageIndex = workspace.getPageIndexForScreenId(screenId);
                 col += (pageIndex % panelCount) * mCountX;
             }
-            return getContext().getString(R.string.move_to_empty_cell, row, col);
+            return getContext().getString(R.string.move_to_empty_cell_description, row, col,
+                    workspace.getPageDescription(pageIndex));
         }
     }
 
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index b276397..1bc269d 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -115,7 +115,6 @@
     private final int extraSpace;
     public int workspaceTopPadding;
     public int workspaceBottomPadding;
-    public int extraHotseatBottomPadding;
 
     // Workspace page indicator
     public final int workspacePageIndicatorHeight;
@@ -157,24 +156,22 @@
     public int folderChildDrawablePaddingPx;
 
     // Hotseat
-    public int hotseatBarSizeExtraSpacePx;
     public final int numShownHotseatIcons;
     public int hotseatCellHeightPx;
-    private final int hotseatExtraVerticalSize;
     private final boolean areNavButtonsInline;
     // In portrait: size = height, in landscape: size = width
     public int hotseatBarSizePx;
-    public int hotseatBarTopPaddingPx;
-    public final int hotseatBarBottomPaddingPx;
+    public int hotseatBarBottomSpacePx;
+    public int hotseatQsbSpace;
     public int springLoadedHotseatBarTopMarginPx;
     // Start is the side next to the nav bar, end is the side next to the workspace
     public final int hotseatBarSidePaddingStartPx;
     public final int hotseatBarSidePaddingEndPx;
     public final int hotseatQsbHeight;
+    public final int hotseatQsbVisualHeight;
+    private final int hotseatQsbShadowHeight;
     public int hotseatBorderSpace;
 
-    public final float qsbBottomMarginOriginalPx;
-    public int qsbBottomMarginPx;
     public int qsbWidth; // only used when isQsbInline
 
     // All apps
@@ -222,7 +219,7 @@
     // Insets
     private final Rect mInsets = new Rect();
     public final Rect workspacePadding = new Rect();
-    private final Rect mHotseatPadding = new Rect();
+    private final Rect mHotseatBarPadding = new Rect();
     // When true, nav bar is on the left side of the screen.
     private boolean mIsSeascape;
 
@@ -275,7 +272,7 @@
         widthPx = windowBounds.bounds.width();
         heightPx = windowBounds.bounds.height();
         availableWidthPx = windowBounds.availableSize.x;
-        availableHeightPx =  windowBounds.availableSize.y;
+        availableHeightPx = windowBounds.availableSize.y;
 
         aspectRatio = ((float) Math.max(widthPx, heightPx)) / Math.min(widthPx, heightPx);
         boolean isTallDevice = Float.compare(aspectRatio, TALL_DEVICE_ASPECT_RATIO_THRESHOLD) >= 0;
@@ -360,6 +357,9 @@
         workspaceCellPaddingXPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_padding_x);
 
         hotseatQsbHeight = res.getDimensionPixelSize(R.dimen.qsb_widget_height);
+        hotseatQsbShadowHeight = res.getDimensionPixelSize(R.dimen.qsb_shadow_height);
+        hotseatQsbVisualHeight = hotseatQsbHeight - 2 * hotseatQsbShadowHeight;
+
         // Whether QSB might be inline in appropriate orientation (e.g. landscape).
         boolean canQsbInline = (isTwoPanels ? inv.inlineQsb[INDEX_TWO_PANEL_PORTRAIT]
                 || inv.inlineQsb[INDEX_TWO_PANEL_LANDSCAPE]
@@ -379,17 +379,28 @@
 
         numShownAllAppsColumns =
                 isTwoPanels ? inv.numDatabaseAllAppsColumns : inv.numAllAppsColumns;
-        hotseatBarSizeExtraSpacePx = 0;
-        hotseatBarTopPaddingPx =
-                res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_top_padding);
-        if (isQsbInline) {
-            hotseatBarBottomPaddingPx = res.getDimensionPixelSize(R.dimen.inline_qsb_bottom_margin);
+
+        int hotseatBarBottomSpace = pxFromDp(inv.hotseatBarBottomSpace[mTypeIndex], mMetrics);
+        int minQsbMargin = res.getDimensionPixelSize(R.dimen.min_qsb_margin);
+        hotseatQsbSpace = pxFromDp(inv.hotseatQsbSpace[mTypeIndex], mMetrics);
+        // Have a little space between the inset and the QSB
+        if (mInsets.bottom + minQsbMargin > hotseatBarBottomSpace) {
+            int availableSpace = hotseatQsbSpace - (mInsets.bottom - hotseatBarBottomSpace);
+
+            // Only change the spaces if there is space
+            if (availableSpace > 0) {
+                // Make sure there is enough space between hotseat/QSB and QSB/navBar
+                if (availableSpace < minQsbMargin * 2) {
+                    minQsbMargin = availableSpace / 2;
+                    hotseatQsbSpace = minQsbMargin;
+                } else {
+                    hotseatQsbSpace -= minQsbMargin;
+                }
+            }
+            hotseatBarBottomSpacePx = mInsets.bottom + minQsbMargin;
+
         } else {
-            hotseatBarBottomPaddingPx = (isTallDevice ? res.getDimensionPixelSize(
-                    R.dimen.dynamic_grid_hotseat_bottom_tall_padding)
-                    : res.getDimensionPixelSize(
-                            R.dimen.dynamic_grid_hotseat_bottom_non_tall_padding))
-                    + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_padding);
+            hotseatBarBottomSpacePx = hotseatBarBottomSpace;
         }
 
         springLoadedHotseatBarTopMarginPx = res.getDimensionPixelSize(
@@ -398,13 +409,7 @@
                 res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_side_padding);
         // Add a bit of space between nav bar and hotseat in vertical bar layout.
         hotseatBarSidePaddingStartPx = isVerticalBarLayout() ? workspacePageIndicatorHeight : 0;
-        hotseatExtraVerticalSize =
-                res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_extra_vertical_size);
-        updateHotseatIconSize(pxFromDp(inv.iconSize[INDEX_DEFAULT], mMetrics));
-
-        qsbBottomMarginOriginalPx = isScalableGrid
-                ? res.getDimensionPixelSize(R.dimen.scalable_grid_qsb_bottom_margin)
-                : 0;
+        updateHotseatSizes(pxFromDp(inv.iconSize[INDEX_DEFAULT], mMetrics));
 
         overviewTaskMarginPx = res.getDimensionPixelSize(R.dimen.overview_task_margin);
         overviewTaskMarginGridPx = res.getDimensionPixelSize(R.dimen.overview_task_margin_grid);
@@ -414,10 +419,7 @@
         overviewTaskIconDrawableSizeGridPx =
                 res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_drawable_size_grid);
         overviewTaskThumbnailTopMarginPx = overviewTaskIconSizePx + overviewTaskMarginPx * 2;
-        // In vertical bar, use the smaller task margin for the top regardless of mode.
-        overviewActionsTopMarginPx = isVerticalBarLayout()
-                ? overviewTaskMarginPx
-                : res.getDimensionPixelSize(R.dimen.overview_actions_top_margin);
+        overviewActionsTopMarginPx = res.getDimensionPixelSize(R.dimen.overview_actions_top_margin);
         overviewPageSpacing = res.getDimensionPixelSize(R.dimen.overview_page_spacing);
         overviewActionsButtonSpacing = res.getDimensionPixelSize(
                 R.dimen.overview_actions_button_spacing);
@@ -446,42 +448,6 @@
 
             workspaceTopPadding = Math.round(paddingWorkspaceTop * cellScaleToFit);
             workspaceBottomPadding = Math.round(paddingWorkspaceBottom * cellScaleToFit);
-            extraHotseatBottomPadding = Math.round(paddingHotseatBottom * cellScaleToFit);
-
-            hotseatBarSizePx += extraHotseatBottomPadding;
-
-            qsbBottomMarginPx = Math.round(qsbBottomMarginOriginalPx * cellScaleToFit);
-        } else if (!isVerticalBarLayout() && isPhone && isTallDevice) {
-            // We increase the hotseat size when there is extra space.
-
-            if (Float.compare(aspectRatio, TALLER_DEVICE_ASPECT_RATIO_THRESHOLD) >= 0
-                    && extraSpace >= Utilities.dpToPx(TALL_DEVICE_EXTRA_SPACE_THRESHOLD_DP)) {
-                // For taller devices, we will take a piece of the extra space from each row,
-                // and add it to the space above and below the hotseat.
-
-                // For devices with more extra space, we take a larger piece from each cell.
-                int piece = extraSpace < Utilities.dpToPx(TALL_DEVICE_MORE_EXTRA_SPACE_THRESHOLD_DP)
-                        ? 7 : 5;
-
-                int extraSpace = ((getCellSize().y - iconSizePx - iconDrawablePaddingPx * 2)
-                        * inv.numRows) / piece;
-
-                workspaceTopPadding = extraSpace / 8;
-                int halfLeftOver = (extraSpace - workspaceTopPadding) / 2;
-                hotseatBarTopPaddingPx += halfLeftOver;
-                hotseatBarSizeExtraSpacePx = halfLeftOver;
-            } else {
-                // ie. For a display with a large aspect ratio, we can keep the icons on the
-                // workspace in portrait mode closer together by adding more height to the hotseat.
-                // Note: This calculation was created after noticing a pattern in the design spec.
-                hotseatBarSizeExtraSpacePx = getCellSize().y - iconSizePx
-                        - iconDrawablePaddingPx * 2 - workspacePageIndicatorHeight;
-            }
-
-            updateHotseatIconSize(iconSizePx);
-
-            // Recalculate the available dimensions using the new hotseat size.
-            updateAvailableDimensions(res);
         }
 
         int cellLayoutPadding =
@@ -537,22 +503,27 @@
                 : res.getDimensionPixelSize(R.dimen.dynamic_grid_left_right_margin);
     }
 
-    private void updateHotseatIconSize(int hotseatIconSizePx) {
+    /** Updates hotseatCellHeightPx and hotseatBarSizePx */
+    private void updateHotseatSizes(int hotseatIconSizePx) {
         // Ensure there is enough space for folder icons, which have a slightly larger radius.
         hotseatCellHeightPx = (int) Math.ceil(hotseatIconSizePx * ICON_OVERLAP_FACTOR);
+
         if (isVerticalBarLayout()) {
             hotseatBarSizePx = hotseatIconSizePx + hotseatBarSidePaddingStartPx
                     + hotseatBarSidePaddingEndPx;
+        } else if (isQsbInline) {
+            hotseatBarSizePx = Math.max(hotseatIconSizePx, hotseatQsbVisualHeight)
+                    + hotseatBarBottomSpacePx;
         } else {
-            hotseatBarSizePx = hotseatIconSizePx + hotseatBarTopPaddingPx
-                    + hotseatBarBottomPaddingPx + (isScalableGrid ? 0 : hotseatExtraVerticalSize)
-                    + hotseatBarSizeExtraSpacePx;
+            hotseatBarSizePx = hotseatIconSizePx
+                    + hotseatQsbSpace
+                    + hotseatQsbVisualHeight
+                    + hotseatBarBottomSpacePx;
         }
     }
 
     private Point getCellLayoutBorderSpace(InvariantDeviceProfile idp) {
         return getCellLayoutBorderSpace(idp, 1f);
-
     }
 
     private Point getCellLayoutBorderSpace(InvariantDeviceProfile idp, float scale) {
@@ -764,7 +735,7 @@
         // All apps
         updateAllAppsIconSize(scale, res);
 
-        updateHotseatIconSize(iconSizePx);
+        updateHotseatSizes(iconSizePx);
 
         // Folder icon
         folderIconSizePx = IconNormalizer.getNormalizedCircleSize(iconSizePx);
@@ -802,13 +773,13 @@
                 + allAppsBorderSpacePx.y;
         // but width is just the cell,
         // the border is added in #updateAllAppsContainerWidth
-        allAppsCellWidthPx = pxFromDp(inv.allAppsCellSize[mTypeIndex].x, mMetrics, scale);
         if (isScalableGrid) {
             allAppsIconSizePx =
                     pxFromDp(inv.allAppsIconSize[mTypeIndex], mMetrics, scale);
             allAppsIconTextSizePx =
                     pxFromSp(inv.allAppsIconTextSize[mTypeIndex], mMetrics, scale);
             allAppsIconDrawablePaddingPx = iconDrawablePaddingOriginalPx;
+            allAppsCellWidthPx = pxFromDp(inv.allAppsCellSize[mTypeIndex].x, mMetrics, scale);
         } else {
             float invIconSizeDp = inv.allAppsIconSize[mTypeIndex];
             float invIconTextSizeSp = inv.allAppsIconTextSize[mTypeIndex];
@@ -816,6 +787,7 @@
             allAppsIconTextSizePx = (int) (pxFromSp(invIconTextSizeSp, mMetrics) * scale);
             allAppsIconDrawablePaddingPx =
                     res.getDimensionPixelSize(R.dimen.all_apps_icon_drawable_padding);
+            allAppsCellWidthPx = allAppsIconSizePx + (2 * allAppsIconDrawablePaddingPx);
         }
 
         updateAllAppsContainerWidth(res);
@@ -932,15 +904,12 @@
      * Gets the space in px from the bottom of last item in the vertical-bar hotseat to the
      * bottom of the screen.
      */
-    public int getVerticalHotseatLastItemBottomOffset() {
+    private int getVerticalHotseatLastItemBottomOffset() {
         int cellHeight = calculateCellHeight(
-                heightPx - mHotseatPadding.top - mHotseatPadding.bottom, hotseatBorderSpace,
+                heightPx - mHotseatBarPadding.top - mHotseatBarPadding.bottom, hotseatBorderSpace,
                 numShownHotseatIcons);
-        int hotseatSize = (cellHeight * numShownHotseatIcons)
-                + (hotseatBorderSpace * (numShownHotseatIcons - 1));
-        int extraHotseatEndSpacing = (heightPx - hotseatSize) / 2;
         int extraIconEndSpacing = (cellHeight - iconSizePx) / 2;
-        return extraHotseatEndSpacing + extraIconEndSpacing + mHotseatPadding.bottom;
+        return extraIconEndSpacing + mHotseatBarPadding.bottom;
     }
 
     /**
@@ -955,7 +924,7 @@
     /**
      * Gets the scaled bottom of the workspace in px for the spring-loaded edit state.
      */
-    private float getCellLayoutSpringLoadShrunkBottom() {
+    public float getCellLayoutSpringLoadShrunkBottom() {
         int topOfHotseat = hotseatBarSizePx + springLoadedHotseatBarTopMarginPx;
         workspaceSpringLoadShrunkBottom =
                 heightPx - (isVerticalBarLayout() ? getVerticalHotseatLastItemBottomOffset()
@@ -1022,10 +991,11 @@
                 padding.right = hotseatBarSizePx;
             }
         } else {
-            // Pad the bottom of the workspace with search/hotseat bar sizes
-            int hotseatTop = hotseatBarSizePx;
-            int paddingBottom = hotseatTop + workspacePageIndicatorHeight
-                    + workspaceBottomPadding - mWorkspacePageIndicatorOverlapWorkspace;
+            // Pad the bottom of the workspace with hotseat bar
+            // and leave a bit of space in case a widget go all the way down
+            int paddingBottom = hotseatBarSizePx + workspaceBottomPadding
+                    + workspacePageIndicatorHeight - mWorkspacePageIndicatorOverlapWorkspace
+                    - mInsets.bottom;
             int paddingTop = workspaceTopPadding + (isScalableGrid ? 0 : edgeMarginPx);
             int paddingSide = desiredWorkspaceHorizontalMarginPx;
 
@@ -1065,17 +1035,17 @@
                     + diffOverlapFactor), 0);
 
             if (isSeascape()) {
-                mHotseatPadding.set(mInsets.left + hotseatBarSidePaddingStartPx, paddingTop,
+                mHotseatBarPadding.set(mInsets.left + hotseatBarSidePaddingStartPx, paddingTop,
                         hotseatBarSidePaddingEndPx, paddingBottom);
             } else {
-                mHotseatPadding.set(hotseatBarSidePaddingEndPx, paddingTop,
+                mHotseatBarPadding.set(hotseatBarSidePaddingEndPx, paddingTop,
                         mInsets.right + hotseatBarSidePaddingStartPx, paddingBottom);
             }
         } else if (isTaskbarPresent) {
             // Center the QSB vertically with hotseat
-            int hotseatBottomPadding = getHotseatBottomPadding();
-            int hotseatTopPadding =
-                    workspacePadding.bottom - hotseatBottomPadding - hotseatCellHeightPx;
+            int hotseatBarBottomPadding = getHotseatBarBottomPadding();
+            int hotseatBarTopPadding =
+                    hotseatBarSizePx - hotseatBarBottomPadding - hotseatCellHeightPx;
 
             // Push icons to the side
             int additionalQsbSpace = isQsbInline ? qsbWidth + hotseatBorderSpace : 0;
@@ -1086,29 +1056,29 @@
             int hotseatWidth = Math.min(requiredWidth, availableWidthPx - endOffset);
             int sideSpacing = (availableWidthPx - hotseatWidth) / 2;
 
-            mHotseatPadding.set(sideSpacing, hotseatTopPadding, sideSpacing, hotseatBottomPadding);
+            mHotseatBarPadding.set(sideSpacing, hotseatBarTopPadding, sideSpacing,
+                    hotseatBarBottomPadding);
 
             boolean isRtl = Utilities.isRtl(context.getResources());
             if (isRtl) {
-                mHotseatPadding.right += additionalQsbSpace;
+                mHotseatBarPadding.right += additionalQsbSpace;
             } else {
-                mHotseatPadding.left += additionalQsbSpace;
+                mHotseatBarPadding.left += additionalQsbSpace;
             }
 
             if (endOffset > sideSpacing) {
                 int diff = isRtl
                         ? sideSpacing - endOffset
                         : endOffset - sideSpacing;
-                mHotseatPadding.left -= diff;
-                mHotseatPadding.right += diff;
+                mHotseatBarPadding.left -= diff;
+                mHotseatBarPadding.right += diff;
             }
         } else if (isScalableGrid) {
             int sideSpacing = (availableWidthPx - qsbWidth) / 2;
-            mHotseatPadding.set(sideSpacing,
-                    hotseatBarTopPaddingPx,
+            mHotseatBarPadding.set(sideSpacing,
+                    0,
                     sideSpacing,
-                    hotseatBarSizePx - hotseatCellHeightPx - hotseatBarTopPaddingPx
-                            + mInsets.bottom);
+                    getHotseatBarBottomPadding());
         } else {
             // We want the edges of the hotseat to line up with the edges of the workspace, but the
             // icons in the hotseat are a different size, and so don't line up perfectly. To account
@@ -1117,14 +1087,15 @@
             float workspaceCellWidth = (float) widthPx / inv.numColumns;
             float hotseatCellWidth = (float) widthPx / numShownHotseatIcons;
             int hotseatAdjustment = Math.round((workspaceCellWidth - hotseatCellWidth) / 2);
-            mHotseatPadding.set(hotseatAdjustment + workspacePadding.left + cellLayoutPaddingPx.left
-                            + mInsets.left, hotseatBarTopPaddingPx,
+            mHotseatBarPadding.set(
+                    hotseatAdjustment + workspacePadding.left + cellLayoutPaddingPx.left
+                            + mInsets.left,
+                    0,
                     hotseatAdjustment + workspacePadding.right + cellLayoutPaddingPx.right
                             + mInsets.right,
-                    hotseatBarSizePx - hotseatCellHeightPx - hotseatBarTopPaddingPx
-                            + mInsets.bottom);
+                    getHotseatBarBottomPadding());
         }
-        return mHotseatPadding;
+        return mHotseatBarPadding;
     }
 
     /**
@@ -1132,27 +1103,22 @@
      */
     public int getQsbOffsetY() {
         if (isQsbInline) {
-            return hotseatBarBottomPaddingPx;
-        }
-
-        int freeSpace = isTaskbarPresent
-                ? workspacePadding.bottom
-                : hotseatBarSizePx - hotseatCellHeightPx - hotseatQsbHeight;
-
-        if (isScalableGrid && qsbBottomMarginPx > mInsets.bottom) {
-            // Note that taskbarSize = 0 unless isTaskbarPresent.
-            return Math.min(qsbBottomMarginPx + taskbarSize, freeSpace);
+            return getHotseatBarBottomPadding() - ((hotseatQsbHeight - hotseatCellHeightPx) / 2);
+        } else if (isTaskbarPresent) { // QSB on top
+            return hotseatBarSizePx - hotseatQsbHeight + hotseatQsbShadowHeight;
         } else {
-            return (int) (freeSpace * mQsbCenterFactor)
-                    + (isTaskbarPresent ? taskbarSize : mInsets.bottom);
+            return hotseatBarBottomSpacePx - hotseatQsbShadowHeight;
         }
     }
 
-    private int getHotseatBottomPadding() {
-        if (isQsbInline) {
-            return getQsbOffsetY() - (Math.abs(hotseatQsbHeight - hotseatCellHeightPx) / 2);
+    /**
+     * Returns the number of pixels the hotseat is translated from the bottom of the screen.
+     */
+    private int getHotseatBarBottomPadding() {
+        if (isTaskbarPresent) { // QSB on top or inline
+            return hotseatBarBottomSpacePx - (Math.abs(hotseatCellHeightPx - iconSizePx) / 2);
         } else {
-            return (getQsbOffsetY() - taskbarSize) / 2;
+            return hotseatBarSizePx - hotseatCellHeightPx;
         }
     }
 
@@ -1163,7 +1129,7 @@
         int taskbarIconBottomSpace = (taskbarSize - iconSizePx) / 2;
         int launcherIconBottomSpace =
                 Math.min((hotseatCellHeightPx - iconSizePx) / 2, gridVisualizationPaddingY);
-        return getHotseatBottomPadding() + launcherIconBottomSpace - taskbarIconBottomSpace;
+        return getHotseatBarBottomPadding() + launcherIconBottomSpace - taskbarIconBottomSpace;
     }
 
     /**
@@ -1172,7 +1138,7 @@
     public int getOverviewActionsClaimedSpaceBelow() {
         if (isTaskbarPresent && !isGestureMode) {
             // Align vertically to where nav buttons are.
-            return  ((taskbarSize - overviewActionsHeight) / 2) + getTaskbarOffsetY();
+            return ((taskbarSize - overviewActionsHeight) / 2) + getTaskbarOffsetY();
         }
 
         return isTaskbarPresent ? stashedTaskbarSize : mInsets.bottom;
@@ -1354,18 +1320,19 @@
         writer.println(prefix + pxToDpStr("hotseatBarSizePx", hotseatBarSizePx));
         writer.println(prefix + "\tinv.hotseatColumnSpan: " + inv.hotseatColumnSpan[mTypeIndex]);
         writer.println(prefix + pxToDpStr("hotseatCellHeightPx", hotseatCellHeightPx));
-        writer.println(prefix + pxToDpStr("hotseatBarTopPaddingPx", hotseatBarTopPaddingPx));
-        writer.println(prefix + pxToDpStr("hotseatBarBottomPaddingPx", hotseatBarBottomPaddingPx));
+        writer.println(prefix + pxToDpStr("hotseatBarBottomPaddingPx", hotseatBarBottomSpacePx));
         writer.println(prefix + pxToDpStr("hotseatBarSidePaddingStartPx",
                 hotseatBarSidePaddingStartPx));
         writer.println(prefix + pxToDpStr("hotseatBarSidePaddingEndPx",
                 hotseatBarSidePaddingEndPx));
+        writer.println(prefix + pxToDpStr("hotseatQsbSpace", hotseatQsbSpace));
+        writer.println(prefix + pxToDpStr("hotseatQsbHeight", hotseatQsbHeight));
         writer.println(prefix + pxToDpStr("springLoadedHotseatBarTopMarginPx",
                 springLoadedHotseatBarTopMarginPx));
-        writer.println(prefix + pxToDpStr("mHotseatPadding.top", mHotseatPadding.top));
-        writer.println(prefix + pxToDpStr("mHotseatPadding.bottom", mHotseatPadding.bottom));
-        writer.println(prefix + pxToDpStr("mHotseatPadding.left", mHotseatPadding.left));
-        writer.println(prefix + pxToDpStr("mHotseatPadding.right", mHotseatPadding.right));
+        writer.println(prefix + pxToDpStr("mHotseatBarPadding.top", mHotseatBarPadding.top));
+        writer.println(prefix + pxToDpStr("mHotseatBarPadding.bottom", mHotseatBarPadding.bottom));
+        writer.println(prefix + pxToDpStr("mHotseatBarPadding.left", mHotseatBarPadding.left));
+        writer.println(prefix + pxToDpStr("mHotseatBarPadding.right", mHotseatBarPadding.right));
         writer.println(prefix + "\tnumShownHotseatIcons: " + numShownHotseatIcons);
         writer.println(prefix + pxToDpStr("hotseatBorderSpace", hotseatBorderSpace));
         writer.println(prefix + "\tisQsbInline: " + isQsbInline);
@@ -1394,7 +1361,6 @@
         }
         writer.println(prefix + pxToDpStr("workspaceTopPadding", workspaceTopPadding));
         writer.println(prefix + pxToDpStr("workspaceBottomPadding", workspaceBottomPadding));
-        writer.println(prefix + pxToDpStr("extraHotseatBottomPadding", extraHotseatBottomPadding));
 
         writer.println(prefix + pxToDpStr("overviewTaskMarginPx", overviewTaskMarginPx));
         writer.println(prefix + pxToDpStr("overviewTaskMarginGridPx", overviewTaskMarginGridPx));
@@ -1430,6 +1396,8 @@
                 workspaceSpringLoadedMinNextPageVisiblePx));
         writer.println(
                 prefix + pxToDpStr("getWorkspaceSpringLoadScale()", getWorkspaceSpringLoadScale()));
+        writer.println(prefix + pxToDpStr("getCellLayoutHeight()", getCellLayoutHeight()));
+        writer.println(prefix + pxToDpStr("getCellLayoutWidth()", getCellLayoutWidth()));
     }
 
     private static Context getContext(Context c, Info info, int orientation, WindowBounds bounds) {
diff --git a/src/com/android/launcher3/DropTargetBar.java b/src/com/android/launcher3/DropTargetBar.java
index d908440..c1304d4 100644
--- a/src/com/android/launcher3/DropTargetBar.java
+++ b/src/com/android/launcher3/DropTargetBar.java
@@ -194,9 +194,10 @@
             int widthSpec = MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST);
             firstButton.measure(widthSpec, heightSpec);
             if (!mIsVertical) {
-                // Remove icons and put the button's text on two lines if text is truncated.
+                // Remove both icons and put the button's text on two lines if text is truncated.
                 if (firstButton.isTextTruncated(availableWidth)) {
                     firstButton.setIconVisible(false);
+                    secondButton.setIconVisible(false);
                     firstButton.setTextMultiLine(true);
                     firstButton.setPadding(horizontalPadding, verticalPadding / 2,
                             horizontalPadding, verticalPadding / 2);
@@ -209,8 +210,10 @@
             }
             secondButton.measure(widthSpec, heightSpec);
             if (!mIsVertical) {
+                // Remove both icons and put the button's text on two lines if text is truncated.
                 if (secondButton.isTextTruncated(availableWidth)) {
                     secondButton.setIconVisible(false);
+                    firstButton.setIconVisible(false);
                     secondButton.setTextMultiLine(true);
                     secondButton.setPadding(horizontalPadding, verticalPadding / 2,
                             horizontalPadding, verticalPadding / 2);
diff --git a/src/com/android/launcher3/FastScrollRecyclerView.java b/src/com/android/launcher3/FastScrollRecyclerView.java
index f117069..94903f2 100644
--- a/src/com/android/launcher3/FastScrollRecyclerView.java
+++ b/src/com/android/launcher3/FastScrollRecyclerView.java
@@ -24,6 +24,7 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 
 import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.launcher3.compat.AccessibilityManagerCompat;
@@ -86,15 +87,20 @@
      * Returns the available scroll height:
      *   AvailableScrollHeight = Total height of the all items - last page height
      */
-    protected abstract int getAvailableScrollHeight();
+    protected int getAvailableScrollHeight() {
+        // AvailableScrollHeight = Total height of the all items - first page height
+        int firstPageHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
+        int totalHeightOfAllItems = getItemsHeight(/* untilIndex= */ getAdapter().getItemCount());
+        int availableScrollHeight = totalHeightOfAllItems - firstPageHeight;
+        return Math.max(0, availableScrollHeight);
+    }
 
     /**
      * Returns the available scroll bar height:
      *   AvailableScrollBarHeight = Total height of the visible view - thumb height
      */
     protected int getAvailableScrollBarHeight() {
-        int availableScrollBarHeight = getScrollbarTrackHeight() - mScrollbar.getThumbHeight();
-        return availableScrollBarHeight;
+        return getScrollbarTrackHeight() - mScrollbar.getThumbHeight();
     }
 
     /**
@@ -152,12 +158,51 @@
     }
 
     /**
-     * Maps the touch (from 0..1) to the adapter position that should be visible.
-     * <p>Override in each subclass of this base class.
-     *
      * @return the scroll top of this recycler view.
      */
-    public abstract int getCurrentScrollY();
+    public int getCurrentScrollY() {
+        Adapter adapter = getAdapter();
+        if (adapter == null) {
+            return -1;
+        }
+        if (adapter.getItemCount() == 0 || getChildCount() == 0) {
+            return -1;
+        }
+
+        int itemPosition = NO_POSITION;
+        View child = null;
+
+        LayoutManager layoutManager = getLayoutManager();
+        if (layoutManager instanceof LinearLayoutManager) {
+            // Use the LayoutManager as the source of truth for visible positions. During
+            // animations, the view group child may not correspond to the visible views that appear
+            // at the top.
+            itemPosition = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();
+            child = layoutManager.findViewByPosition(itemPosition);
+        }
+
+        if (child == null) {
+            // If the layout manager returns null for any reason, which can happen before layout
+            // has occurred for the position, then look at the child of this view as a ViewGroup.
+            child = getChildAt(0);
+            itemPosition = getChildAdapterPosition(child);
+        }
+        if (itemPosition == NO_POSITION) {
+            return -1;
+        }
+        return getPaddingTop() + getItemsHeight(itemPosition)
+                - layoutManager.getDecoratedTop(child);
+    }
+
+    /**
+     * Returns the sum of the height, in pixels, of this list adapter's items from index
+     * 0 (inclusive) until {@code untilIndex} (exclusive). If untilIndex is same as the itemCount,
+     * it returns the full height of all the items.
+     *
+     * <p>If the untilIndex is larger than the total number of items in this adapter, returns the
+     * sum of all items' height.
+     */
+    protected abstract int getItemsHeight(int untilIndex);
 
     /**
      * Maps the touch (from 0..1) to the adapter position that should be visible.
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 76106fc..05ed319 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -111,9 +111,7 @@
             mQsb.setVisibility(View.VISIBLE);
             lp.gravity = Gravity.BOTTOM;
             lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
-            lp.height = grid.isTaskbarPresent
-                    ? grid.workspacePadding.bottom
-                    : grid.hotseatBarSizePx + insets.bottom;
+            lp.height = grid.hotseatBarSizePx;
         }
 
         Rect padding = grid.getHotseatLayoutPadding(getContext());
@@ -206,6 +204,13 @@
         getShortcutsAndWidgets().setAlpha(alpha);
     }
 
+    /**
+     * Sets the alpha value of just our QSB.
+     */
+    public void setQsbAlpha(float alpha) {
+        mQsb.setAlpha(alpha);
+    }
+
     public float getIconsAlpha() {
         return getShortcutsAndWidgets().getAlpha();
     }
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index db43b44..2085b84 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -46,6 +46,7 @@
 import androidx.annotation.IntDef;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
+import androidx.core.content.res.ResourcesCompat;
 
 import com.android.launcher3.model.DeviceGridState;
 import com.android.launcher3.provider.RestoreDbTask;
@@ -61,8 +62,6 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.StringWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -79,7 +78,8 @@
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({TYPE_PHONE, TYPE_MULTI_DISPLAY, TYPE_TABLET})
-    public @interface DeviceType{}
+    public @interface DeviceType {}
+
     public static final int TYPE_PHONE = 0;
     public static final int TYPE_MULTI_DISPLAY = 1;
     public static final int TYPE_TABLET = 2;
@@ -155,6 +155,8 @@
     public int numDatabaseHotseatIcons;
 
     public int[] hotseatColumnSpan;
+    public float[] hotseatBarBottomSpace;
+    public float[] hotseatQsbSpace;
 
     /**
      * Number of columns in the all apps list.
@@ -234,7 +236,8 @@
                         /*allowDisabledGrid=*/false),
                 defaultDeviceType);
 
-        Info myInfo = new Info(context, display);
+        Context displayContext = context.createDisplayContext(display);
+        Info myInfo = new Info(displayContext);
         @DeviceType int deviceType = getDeviceType(myInfo);
         DisplayOption myDisplayOption = invDistWeightedInterpolate(
                 myInfo,
@@ -361,6 +364,8 @@
                 ? closestProfile.numDatabaseHotseatIcons : closestProfile.numHotseatIcons;
         hotseatColumnSpan = closestProfile.hotseatColumnSpan;
         hotseatBorderSpaces = displayOption.hotseatBorderSpaces;
+        hotseatBarBottomSpace = displayOption.hotseatBarBottomSpace;
+        hotseatQsbSpace = displayOption.hotseatQsbSpace;
 
         numAllAppsColumns = closestProfile.numAllAppsColumns;
         numDatabaseAllAppsColumns = deviceType == TYPE_MULTI_DISPLAY
@@ -633,18 +638,6 @@
         float screenHeight = config.screenHeightDp * res.getDisplayMetrics().density;
         int rotation = WindowManagerProxy.INSTANCE.get(context).getRotation(context);
 
-        if (Utilities.IS_DEBUG_DEVICE) {
-            StringWriter stringWriter = new StringWriter();
-            PrintWriter printWriter = new PrintWriter(stringWriter);
-            DisplayController.INSTANCE.get(context).dump(printWriter);
-            printWriter.flush();
-            Log.d("b/231312158", "getDeviceProfile -"
-                            + "\nconfig: " + config
-                            + "\ndisplayMetrics: " + res.getDisplayMetrics()
-                            + "\nrotation: " + rotation
-                            + "\n" + stringWriter.toString(),
-                    new Exception());
-        }
         return getBestMatch(screenWidth, screenHeight, rotation);
     }
 
@@ -739,6 +732,7 @@
         private final int numHotseatIcons;
         private final int numShrunkenHotseatIcons;
         private final int numDatabaseHotseatIcons;
+
         private final int[] hotseatColumnSpan = new int[COUNT_SIZES];
 
         private final String dbFile;
@@ -779,6 +773,7 @@
                     R.styleable.GridDisplayOption_numShrunkenHotseatIcons, numHotseatIcons / 2);
             numDatabaseHotseatIcons = a.getInt(
                     R.styleable.GridDisplayOption_numExtendedHotseatIcons, 2 * numHotseatIcons);
+
             hotseatColumnSpan[INDEX_DEFAULT] = a.getInt(
                     R.styleable.GridDisplayOption_hotseatColumnSpan, numColumns);
             hotseatColumnSpan[INDEX_LANDSCAPE] = a.getInt(
@@ -838,6 +833,8 @@
         private final float[] horizontalMargin = new float[COUNT_SIZES];
         //TODO(http://b/228998082) remove this when 3 button spaces are fixed
         private final float[] hotseatBorderSpaces = new float[COUNT_SIZES];
+        private final float[] hotseatBarBottomSpace = new float[COUNT_SIZES];
+        private final float[] hotseatQsbSpace = new float[COUNT_SIZES];
 
         private final float[] iconSizes = new float[COUNT_SIZES];
         private final float[] textSizes = new float[COUNT_SIZES];
@@ -1063,6 +1060,34 @@
                     R.styleable.ProfileDisplayOption_hotseatBorderSpaceTwoPanelPortrait,
                     hotseatBorderSpaces[INDEX_DEFAULT]);
 
+            hotseatBarBottomSpace[INDEX_DEFAULT] = a.getFloat(
+                    R.styleable.ProfileDisplayOption_hotseatBarBottomSpace,
+                    ResourcesCompat.getFloat(context.getResources(),
+                            R.dimen.hotseat_bar_bottom_space_default));
+            hotseatBarBottomSpace[INDEX_LANDSCAPE] = a.getFloat(
+                    R.styleable.ProfileDisplayOption_hotseatBarBottomSpaceLandscape,
+                    hotseatBarBottomSpace[INDEX_DEFAULT]);
+            hotseatBarBottomSpace[INDEX_TWO_PANEL_LANDSCAPE] = a.getFloat(
+                    R.styleable.ProfileDisplayOption_hotseatBarBottomSpaceTwoPanelLandscape,
+                    hotseatBarBottomSpace[INDEX_DEFAULT]);
+            hotseatBarBottomSpace[INDEX_TWO_PANEL_PORTRAIT] = a.getFloat(
+                    R.styleable.ProfileDisplayOption_hotseatBarBottomSpaceTwoPanelPortrait,
+                    hotseatBarBottomSpace[INDEX_DEFAULT]);
+
+            hotseatQsbSpace[INDEX_DEFAULT] = a.getFloat(
+                    R.styleable.ProfileDisplayOption_hotseatQsbSpace,
+                    ResourcesCompat.getFloat(context.getResources(),
+                            R.dimen.hotseat_qsb_space_default));
+            hotseatQsbSpace[INDEX_LANDSCAPE] = a.getFloat(
+                    R.styleable.ProfileDisplayOption_hotseatQsbSpaceLandscape,
+                    hotseatQsbSpace[INDEX_DEFAULT]);
+            hotseatQsbSpace[INDEX_TWO_PANEL_LANDSCAPE] = a.getFloat(
+                    R.styleable.ProfileDisplayOption_hotseatQsbSpaceTwoPanelLandscape,
+                    hotseatQsbSpace[INDEX_DEFAULT]);
+            hotseatQsbSpace[INDEX_TWO_PANEL_PORTRAIT] = a.getFloat(
+                    R.styleable.ProfileDisplayOption_hotseatQsbSpaceTwoPanelPortrait,
+                    hotseatQsbSpace[INDEX_DEFAULT]);
+
             a.recycle();
         }
 
@@ -1098,6 +1123,8 @@
                 minCellSize[i].y *= w;
                 horizontalMargin[i] *= w;
                 hotseatBorderSpaces[i] *= w;
+                hotseatBarBottomSpace[i] *= w;
+                hotseatQsbSpace[i] *= w;
                 allAppsCellSize[i].x *= w;
                 allAppsCellSize[i].y *= w;
                 allAppsIconSizes[i] *= w;
@@ -1121,6 +1148,8 @@
                 minCellSize[i].y += p.minCellSize[i].y;
                 horizontalMargin[i] += p.horizontalMargin[i];
                 hotseatBorderSpaces[i] += p.hotseatBorderSpaces[i];
+                hotseatBarBottomSpace[i] += p.hotseatBarBottomSpace[i];
+                hotseatQsbSpace[i] += p.hotseatQsbSpace[i];
                 allAppsCellSize[i].x += p.allAppsCellSize[i].x;
                 allAppsCellSize[i].y += p.allAppsCellSize[i].y;
                 allAppsIconSizes[i] += p.allAppsIconSizes[i];
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index ebed31b..5081f4f 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -33,7 +33,6 @@
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
 import static com.android.launcher3.LauncherState.ALL_APPS;
-import static com.android.launcher3.LauncherState.FLAG_CLOSE_POPUPS;
 import static com.android.launcher3.LauncherState.FLAG_MULTI_PAGE;
 import static com.android.launcher3.LauncherState.FLAG_NON_INTERACTIVE;
 import static com.android.launcher3.LauncherState.NORMAL;
@@ -266,7 +265,7 @@
     protected static final int REQUEST_LAST = 100;
 
     // Type: int
-    private static final String RUNTIME_STATE = "launcher.state";
+    protected static final String RUNTIME_STATE = "launcher.state";
     // Type: PendingRequestArgs
     private static final String RUNTIME_STATE_PENDING_REQUEST_ARGS = "launcher.request_args";
     // Type: int
@@ -278,6 +277,9 @@
     // Type int[]
     private static final String RUNTIME_STATE_CURRENT_SCREEN_IDS = "launcher.current_screen_ids";
 
+    // Type PendingSplitSelectInfo<Parcelable>
+    protected static final String PENDING_SPLIT_SELECT_INFO = "launcher.pending_split_select_info";
+
     public static final String ON_CREATE_EVT = "Launcher.onCreate";
     public static final String ON_START_EVT = "Launcher.onStart";
     public static final String ON_RESUME_EVT = "Launcher.onResume";
@@ -305,7 +307,6 @@
     Workspace<?> mWorkspace;
     @Thunk
     DragLayer mDragLayer;
-    private DragController mDragController;
 
     private WidgetManagerHelper mAppWidgetManager;
     private LauncherAppWidgetHost mAppWidgetHost;
@@ -369,6 +370,7 @@
     private RotationHelper mRotationHelper;
 
     protected LauncherOverlayManager mOverlayManager;
+    protected DragController mDragController;
     // If true, overlay callbacks are deferred
     private boolean mDeferOverlayCallbacks;
     private final Runnable mDeferredOverlayCallbacks = this::checkIfOverlayStillDeferred;
@@ -465,7 +467,7 @@
         mIconCache = app.getIconCache();
         mAccessibilityDelegate = createAccessibilityDelegate();
 
-        mDragController = new LauncherDragController(this);
+        initDragController();
         mAllAppsController = new AllAppsTransitionController(this);
         mStateManager = new StateManager<>(this, NORMAL);
 
@@ -614,6 +616,13 @@
         super.onConfigurationChanged(newConfig);
     }
 
+    /**
+     * Initializes the drag controller.
+     */
+    protected void initDragController() {
+        mDragController = new LauncherDragController(this);
+    }
+
     @Override
     public void onIdpChanged(boolean modelPropertiesChanged) {
         initDeviceProfile(mDeviceProfile.inv);
@@ -740,7 +749,7 @@
                 completeAddAppWidget(appWidgetId, info, null, null);
                 break;
             case REQUEST_RECONFIGURE_APPWIDGET:
-                mStatsLogManager.logger().withItemInfo(info).log(LAUNCHER_WIDGET_RECONFIGURED);
+                getStatsLogManager().logger().withItemInfo(info).log(LAUNCHER_WIDGET_RECONFIGURED);
                 completeRestoreAppWidget(appWidgetId, LauncherAppWidgetInfo.RESTORE_COMPLETED);
                 break;
             case REQUEST_BIND_PENDING_APPWIDGET: {
@@ -1080,10 +1089,6 @@
         }
         addActivityFlags(ACTIVITY_STATE_TRANSITION_ACTIVE);
 
-        if (state.hasFlag(FLAG_CLOSE_POPUPS)) {
-            AbstractFloatingView.closeAllOpenViews(this, !state.hasFlag(FLAG_NON_INTERACTIVE));
-        }
-
         if (state == SPRING_LOADED) {
             // Prevent any Un/InstallShortcutReceivers from updating the db while we are
             // not on homescreen
diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java
index 808bf96..0c7c311 100644
--- a/src/com/android/launcher3/LauncherAnimUtils.java
+++ b/src/com/android/launcher3/LauncherAnimUtils.java
@@ -26,6 +26,7 @@
 import android.util.IntProperty;
 import android.view.View;
 import android.view.ViewGroup.LayoutParams;
+import android.widget.TextView;
 
 import com.android.launcher3.util.MultiScalePropertyFactory;
 
@@ -115,6 +116,32 @@
                 }
             };
 
+    public static final IntProperty<TextView> TEXT_COLOR =
+            new IntProperty<TextView>("textColor") {
+                @Override
+                public Integer get(TextView view) {
+                    return view.getTextColors().getDefaultColor();
+                }
+
+                @Override
+                public void setValue(TextView view, int color) {
+                    view.setTextColor(color);
+                }
+            };
+
+    public static final IntProperty<TextView> HINT_TEXT_COLOR =
+            new IntProperty<TextView>("hintTextColor") {
+                @Override
+                public Integer get(TextView view) {
+                    return view.getHintTextColors().getDefaultColor();
+                }
+
+                @Override
+                public void setValue(TextView view, int color) {
+                    view.setHintTextColor(color);
+                }
+            };
+
     public static final FloatProperty<View> VIEW_TRANSLATE_X =
             View.TRANSLATION_X instanceof FloatProperty ? (FloatProperty) View.TRANSLATION_X
                     : new FloatProperty<View>("translateX") {
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 5aa8a46..a20ff8c 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -162,7 +162,7 @@
     private synchronized boolean prepForMigration(String dbFile, String targetTableName,
             Supplier<DatabaseHelper> src, Supplier<DatabaseHelper> dst) {
         if (TextUtils.equals(dbFile, mOpenHelper.getDatabaseName())) {
-            Log.e("b/198965093", "prepForMigration - target db is same as current: " + dbFile);
+            Log.e(TAG, "prepForMigration - target db is same as current: " + dbFile);
             return false;
         }
 
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index cba0b7d..73be5be 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -1187,9 +1187,7 @@
     }
 
     public int getScrollForPage(int index) {
-        // TODO(b/233112195): Use !pageScrollsInitialized() instead of mPageScrolls == null, once we
-        // root cause where we should be using runOnPageScrollsInitialized().
-        if (mPageScrolls == null || index >= mPageScrolls.length || index < 0) {
+        if (!pageScrollsInitialized() || index >= mPageScrolls.length || index < 0) {
             return 0;
         } else {
             return mPageScrolls[index];
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 7b96838..dc8c739 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -587,15 +587,6 @@
     }
 
     /**
-     * Returns an intent for starting the default home activity
-     */
-    public static Intent createHomeIntent() {
-        return new Intent(Intent.ACTION_MAIN)
-                .addCategory(Intent.CATEGORY_HOME)
-                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-    }
-
-    /**
      * Wraps a message with a TTS span, so that a different message is spoken than
      * what is getting displayed.
      * @param msg original message
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 4903d77..e6dea8f 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -159,7 +159,7 @@
     private LayoutTransition mLayoutTransition;
     @Thunk final WallpaperManager mWallpaperManager;
 
-    private ShortcutAndWidgetContainer mDragSourceInternal;
+    protected ShortcutAndWidgetContainer mDragSourceInternal;
 
     @Thunk final IntSparseArrayMap<CellLayout> mWorkspaceScreens = new IntSparseArrayMap<>();
     @Thunk final IntArray mScreenOrder = new IntArray();
@@ -195,7 +195,7 @@
     @Thunk final Launcher mLauncher;
     @Thunk DragController mDragController;
 
-    private final int[] mTempXY = new int[2];
+    protected final int[] mTempXY = new int[2];
     private final float[] mTempFXY = new float[2];
     private final Rect mTempRect = new Rect();
     @Thunk float[] mDragViewVisualCenter = new float[2];
@@ -3418,7 +3418,11 @@
         return getPageDescription(page);
     }
 
-    private String getPageDescription(int page) {
+    /**
+     * @param page page index.
+     * @return Description of the page at the given page index.
+     */
+    public String getPageDescription(int page) {
         int nScreens = getChildCount();
         int extraScreenId = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
         if (extraScreenId >= 0 && nScreens > 1) {
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index af17cf7..de34416 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -31,7 +31,6 @@
 import android.util.Log;
 import android.util.SparseIntArray;
 import android.view.MotionEvent;
-import android.view.View;
 
 import androidx.recyclerview.widget.RecyclerView;
 
@@ -342,24 +341,7 @@
     }
 
     @Override
-    public int getCurrentScrollY() {
-        // Return early if there are no items or we haven't been measured
-        List<AllAppsGridAdapter.AdapterItem> items = mApps.getAdapterItems();
-        if (items.isEmpty() || mNumAppsPerRow == 0 || getChildCount() == 0) {
-            return -1;
-        }
-
-        // Calculate the y and offset for the item
-        View child = getChildAt(0);
-        int position = getChildAdapterPosition(child);
-        if (position == NO_POSITION) {
-            return -1;
-        }
-        return getPaddingTop() +
-                getCurrentScrollY(position, getLayoutManager().getDecoratedTop(child));
-    }
-
-    public int getCurrentScrollY(int position, int offset) {
+    protected int getItemsHeight(int position) {
         List<AllAppsGridAdapter.AdapterItem> items = mApps.getAdapterItems();
         AllAppsGridAdapter.AdapterItem posItem = position < items.size()
                 ? items.get(position) : null;
@@ -400,17 +382,7 @@
             }
             mCachedScrollPositions.put(position, y);
         }
-        return y - offset;
-    }
-
-    /**
-     * Returns the available scroll height:
-     * AvailableScrollHeight = Total height of the all items - last page height
-     */
-    @Override
-    protected int getAvailableScrollHeight() {
-        return getPaddingTop() + getCurrentScrollY(getAdapter().getItemCount(), 0)
-                - getHeight() + getPaddingBottom();
+        return y;
     }
 
     public int getScrollBarTop() {
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index a4a2085..e0f1b3d 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -23,7 +23,6 @@
 import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
-import static com.android.launcher3.util.SystemUiController.UI_STATE_ALLAPPS;
 
 import android.animation.Animator;
 import android.animation.Animator.AnimatorListener;
@@ -37,7 +36,6 @@
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorListeners;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.anim.PropertySetter;
@@ -293,11 +291,6 @@
     public void setupViews(ScrimView scrimView, ActivityAllAppsContainerView<Launcher> appsView) {
         mScrimView = scrimView;
         mAppsView = appsView;
-        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && Utilities.ATLEAST_R) {
-            mLauncher.getSystemUiController().updateUiState(UI_STATE_ALLAPPS,
-                    View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
-                            | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
-        }
         mAppsView.setScrimView(scrimView);
         mAppsViewAlpha = new MultiValueAlpha(mAppsView, APPS_VIEW_INDEX_COUNT);
         mAppsViewAlpha.setUpdateVisibility(true);
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
index ecadec6..72a9b14 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
@@ -253,7 +253,7 @@
     public boolean shouldContainerScroll(MotionEvent ev) {
         BaseDragLayer dragLayer = mActivityContext.getDragLayer();
         // Scroll if not within the container view (e.g. over large-screen scrim).
-        if (!dragLayer.isEventOverView(this, ev)) {
+        if (!dragLayer.isEventOverView(getVisibleContainerView(), ev)) {
             return true;
         }
         if (dragLayer.isEventOverView(mBottomSheetHandleArea, ev)) {
@@ -304,7 +304,8 @@
             mTouchHandler.handleTouchEvent(ev, mFastScrollerOffset);
             return true;
         }
-        if (isSearching()) {
+        if (isSearching()
+                && mActivityContext.getDragLayer().isEventOverView(getVisibleContainerView(), ev)) {
             // if in search state, consume touch event.
             return true;
         }
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderRow.java b/src/com/android/launcher3/allapps/FloatingHeaderRow.java
index 6ff2132..75e527a 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderRow.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderRow.java
@@ -15,11 +15,8 @@
  */
 package com.android.launcher3.allapps;
 
-import android.graphics.Rect;
 import android.view.View;
 
-import com.android.launcher3.DeviceProfile;
-
 /**
  * A abstract representation of a row in all-apps view
  */
@@ -29,8 +26,6 @@
 
     void setup(FloatingHeaderView parent, FloatingHeaderRow[] allRows, boolean tabsHidden);
 
-    void setInsets(Rect insets, DeviceProfile grid);
-
     int getExpectedHeight();
 
     /**
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index 6ecbad2..c5bdb69 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -17,6 +17,7 @@
 
 import android.animation.ValueAnimator;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.util.ArrayMap;
@@ -30,7 +31,6 @@
 import androidx.annotation.Nullable;
 import androidx.recyclerview.widget.RecyclerView;
 
-import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.BaseAllAppsContainerView.AdapterHolder;
@@ -84,7 +84,7 @@
     // These two values are necessary to ensure that the header protection is drawn correctly.
     private final int mHeaderTopAdjustment;
     private final int mHeaderBottomAdjustment;
-    private final boolean mHeaderProtectionSupported;
+    private boolean mHeaderProtectionSupported;
 
     protected ViewGroup mTabLayout;
     private AllAppsRecyclerView mMainRV;
@@ -122,9 +122,14 @@
         mHeaderBottomAdjustment = context.getResources()
                 .getDimensionPixelSize(R.dimen.all_apps_header_bottom_adjustment);
         mHeaderProtectionSupported = context.getResources().getBoolean(
-                R.bool.config_header_protection_supported)
-                // TODO(b/208599118) Support header protection for bottom sheet.
-                && !ActivityContext.lookupContext(context).getDeviceProfile().isTablet;
+                R.bool.config_header_protection_supported);
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        mHeaderProtectionSupported = getContext().getResources().getBoolean(
+                R.bool.config_header_protection_supported);
     }
 
     @Override
@@ -158,22 +163,6 @@
         PluginManagerWrapper.INSTANCE.get(getContext()).removePluginListener(this);
     }
 
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        mTabLayout.getLayoutParams().width = getTabWidth();
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-    }
-
-    /**
-     * Returns distance between left and right app icons
-     */
-    public int getTabWidth() {
-        DeviceProfile grid = ActivityContext.lookupContext(getContext()).getDeviceProfile();
-        int totalWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
-        int iconPadding = totalWidth / grid.numShownAllAppsColumns - grid.allAppsIconSizePx;
-        return totalWidth - iconPadding - grid.allAppsIconDrawablePaddingPx;
-    }
-
     private void recreateAllRowsArray() {
         int pluginCount = mPluginRows.size();
         if (pluginCount == 0) {
@@ -423,15 +412,6 @@
         p.y = getTop() - mCurrentRV.getTop() - ((ViewGroup) mCurrentRV.getParent()).getTop();
     }
 
-    public boolean hasVisibleContent() {
-        for (FloatingHeaderRow row : mAllRows) {
-            if (row.hasVisibleContent()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     public boolean isHeaderProtectionSupported() {
         return mHeaderProtectionSupported;
     }
@@ -443,10 +423,9 @@
 
     @Override
     public void setInsets(Rect insets) {
-        DeviceProfile grid = ActivityContext.lookupContext(getContext()).getDeviceProfile();
-        for (FloatingHeaderRow row : mAllRows) {
-            row.setInsets(insets, grid);
-        }
+        int leftRightPadding = ActivityContext.lookupContext(getContext())
+                .getDeviceProfile().allAppsLeftRightPadding;
+        setPadding(leftRightPadding, getPaddingTop(), leftRightPadding, getPaddingBottom());
     }
 
     public <T extends FloatingHeaderRow> T findFixedRowByType(Class<T> type) {
diff --git a/src/com/android/launcher3/allapps/PluginHeaderRow.java b/src/com/android/launcher3/allapps/PluginHeaderRow.java
index 5b5fbb7..a9d36d1 100644
--- a/src/com/android/launcher3/allapps/PluginHeaderRow.java
+++ b/src/com/android/launcher3/allapps/PluginHeaderRow.java
@@ -18,10 +18,8 @@
 import static android.view.View.INVISIBLE;
 import static android.view.View.VISIBLE;
 
-import android.graphics.Rect;
 import android.view.View;
 
-import com.android.launcher3.DeviceProfile;
 import com.android.systemui.plugins.AllAppsRow;
 
 /**
@@ -43,9 +41,6 @@
             boolean tabsHidden) { }
 
     @Override
-    public void setInsets(Rect insets, DeviceProfile grid) { }
-
-    @Override
     public int getExpectedHeight() {
         return mPlugin.getExpectedHeight();
     }
diff --git a/src/com/android/launcher3/allapps/WorkEduCard.java b/src/com/android/launcher3/allapps/WorkEduCard.java
index 836cd5a..4feeabb 100644
--- a/src/com/android/launcher3/allapps/WorkEduCard.java
+++ b/src/com/android/launcher3/allapps/WorkEduCard.java
@@ -70,8 +70,6 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         findViewById(R.id.action_btn).setOnClickListener(this);
-        MarginLayoutParams lp = ((MarginLayoutParams) findViewById(R.id.wrapper).getLayoutParams());
-        lp.width = mActivityContext.getAppsView().getFloatingHeaderView().getTabWidth();
     }
 
     @Override
diff --git a/src/com/android/launcher3/allapps/WorkModeSwitch.java b/src/com/android/launcher3/allapps/WorkModeSwitch.java
index 733577e..bd24b77 100644
--- a/src/com/android/launcher3/allapps/WorkModeSwitch.java
+++ b/src/com/android/launcher3/allapps/WorkModeSwitch.java
@@ -22,14 +22,16 @@
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.View;
-import android.view.ViewGroup;
+import android.view.ViewGroup.MarginLayoutParams;
 import android.view.WindowInsets;
 import android.widget.Button;
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Insettable;
+import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.KeyboardInsetAnimationCallback;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.StringCache;
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip;
@@ -50,7 +52,6 @@
     private boolean mWorkEnabled;
     private boolean mOnWorkTab;
 
-
     public WorkModeSwitch(Context context) {
         this(context, null, 0);
     }
@@ -85,16 +86,29 @@
 
     @Override
     public void setInsets(Rect insets) {
-        int bottomInset = insets.bottom - mInsets.bottom;
         mInsets.set(insets);
-        ViewGroup.MarginLayoutParams marginLayoutParams =
-                (ViewGroup.MarginLayoutParams) getLayoutParams();
-        if (marginLayoutParams != null) {
-            marginLayoutParams.bottomMargin = bottomInset + marginLayoutParams.bottomMargin;
+        MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
+        if (lp != null) {
+            int bottomMargin = getResources().getDimensionPixelSize(R.dimen.work_fab_margin_bottom);
+            if (FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get()) {
+                bottomMargin <<= 1;  // Double margin to add space above search bar.
+                bottomMargin += getResources().getDimensionPixelSize(R.dimen.qsb_widget_height);
+            }
+
+            DeviceProfile dp = ActivityContext.lookupContext(getContext()).getDeviceProfile();
+            lp.rightMargin = lp.leftMargin = dp.allAppsLeftRightPadding;
+            if (!dp.isGestureMode) {
+                if (dp.isTaskbarPresent) {
+                    bottomMargin += dp.taskbarSize;
+                } else {
+                    bottomMargin += insets.bottom;
+                }
+            }
+
+            lp.bottomMargin = bottomMargin;
         }
     }
 
-
     @Override
     public void onActivePageChanged(int page) {
         mOnWorkTab = page == ActivityAllAppsContainerView.AdapterHolder.WORK;
diff --git a/src/com/android/launcher3/allapps/WorkProfileManager.java b/src/com/android/launcher3/allapps/WorkProfileManager.java
index b70cb13..2f5b7a2 100644
--- a/src/com/android/launcher3/allapps/WorkProfileManager.java
+++ b/src/com/android/launcher3/allapps/WorkProfileManager.java
@@ -26,7 +26,6 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Log;
-import android.view.ViewGroup;
 
 import androidx.annotation.IntDef;
 import androidx.annotation.Nullable;
@@ -34,7 +33,6 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip;
 
@@ -144,24 +142,6 @@
             mWorkModeSwitch = (WorkModeSwitch) mAllApps.getLayoutInflater().inflate(
                     R.layout.work_mode_fab, mAllApps, false);
         }
-        ViewGroup.MarginLayoutParams lp =
-                (ViewGroup.MarginLayoutParams) mWorkModeSwitch.getLayoutParams();
-        int workFabMarginBottom =
-                mWorkModeSwitch.getResources().getDimensionPixelSize(
-                        R.dimen.work_fab_margin_bottom);
-        if (FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get()) {
-            workFabMarginBottom <<= 1;  // Double margin to add space above search bar.
-            workFabMarginBottom +=
-                    mWorkModeSwitch.getResources().getDimensionPixelSize(R.dimen.qsb_widget_height);
-        }
-        if (!mAllApps.mActivityContext.getDeviceProfile().isGestureMode){
-            workFabMarginBottom += mAllApps.mActivityContext.getDeviceProfile().getInsets().bottom;
-        }
-        lp.bottomMargin = workFabMarginBottom;
-        int totalScreenWidth = mDeviceProfile.widthPx;
-        int personalWorkTabWidth =
-                mAllApps.mActivityContext.getAppsView().getFloatingHeaderView().getTabWidth();
-        lp.rightMargin = lp.leftMargin = (totalScreenWidth - personalWorkTabWidth) / 2;
         if (mWorkModeSwitch.getParent() != mAllApps) {
             mAllApps.addView(mWorkModeSwitch);
         }
diff --git a/src/com/android/launcher3/anim/AnimatedPropertySetter.java b/src/com/android/launcher3/anim/AnimatedPropertySetter.java
index e5f5e7c..82e645a 100644
--- a/src/com/android/launcher3/anim/AnimatedPropertySetter.java
+++ b/src/com/android/launcher3/anim/AnimatedPropertySetter.java
@@ -43,9 +43,17 @@
 
     @Override
     public Animator setViewAlpha(View view, float alpha, TimeInterpolator interpolator) {
-        if (view == null || view.getAlpha() == alpha) {
+        if (view == null) {
             return NO_OP;
         }
+
+        // Short-circuit if the view already has this alpha value, but make sure the visibility is
+        // set correctly for the requested alpha.
+        if (Float.compare(view.getAlpha(), alpha) == 0) {
+            AlphaUpdateListener.updateVisibility(view);
+            return NO_OP;
+        }
+
         ObjectAnimator anim = ObjectAnimator.ofFloat(view, View.ALPHA, alpha);
         anim.addListener(new AlphaUpdateListener(view));
         anim.setInterpolator(interpolator);
@@ -89,6 +97,18 @@
         return anim;
     }
 
+    @NonNull
+    @Override
+    public <T> Animator setColor(T target, IntProperty<T> property, int value,
+            TimeInterpolator interpolator) {
+        if (property.get(target) == value) {
+            return NO_OP;
+        }
+        Animator anim = ObjectAnimator.ofArgb(target, property, value);
+        anim.setInterpolator(interpolator);
+        add(anim);
+        return anim;
+    }
 
     /**
      * Adds a callback to be run on every frame of the animation
diff --git a/src/com/android/launcher3/anim/PropertySetter.java b/src/com/android/launcher3/anim/PropertySetter.java
index d2207f6..b0ed2d2 100644
--- a/src/com/android/launcher3/anim/PropertySetter.java
+++ b/src/com/android/launcher3/anim/PropertySetter.java
@@ -89,6 +89,16 @@
     }
 
     /**
+     * Updates a color property of the target using the provided interpolator
+     */
+    @NonNull
+    public <T> Animator setColor(T target, IntProperty<T> property, int value,
+            TimeInterpolator interpolator) {
+        property.setValue(target, value);
+        return NO_OP;
+    }
+
+    /**
      * Runs the animation as part of setting the property
      */
     public abstract void add(Animator animatorSet);
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 33beacd..b7f3dad 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -96,6 +96,9 @@
     public static final BooleanFlag ENABLE_QUICK_SEARCH = new DeviceFlag("ENABLE_QUICK_SEARCH",
             true, "Use quick search behavior.");
 
+    public static final BooleanFlag ENABLE_HIDE_HEADER = new DeviceFlag("ENABLE_HIDE_HEADER",
+            true, "Hide header on keyboard before typing in all apps");
+
     public static final BooleanFlag COLLECT_SEARCH_HISTORY = new DeviceFlag(
             "COLLECT_SEARCH_HISTORY", false, "Allow launcher to collect search history for log");
 
@@ -209,9 +212,6 @@
             "ENABLE_SCRIM_FOR_APP_LAUNCH", false,
             "Enables scrim during app launch animation.");
 
-    public static final BooleanFlag ENABLE_SPLIT_SELECT = getDebugFlag(
-            "ENABLE_SPLIT_SELECT", true, "Uses new split screen selection overview UI");
-
     public static final BooleanFlag ENABLE_ENFORCED_ROUNDED_CORNERS = new DeviceFlag(
             "ENABLE_ENFORCED_ROUNDED_CORNERS", true, "Enforce rounded corners on all App Widgets");
 
@@ -277,6 +277,14 @@
             "ENABLE_DISMISS_PREDICTION_UNDO", false,
             "Show an 'Undo' snackbar when users dismiss a predicted hotseat item");
 
+    public static final BooleanFlag ENABLE_CACHED_WIDGET = getDebugFlag(
+            "ENABLE_CACHED_WIDGET", true,
+            "Show previously cached widgets as opposed to deferred widget where available");
+
+    public static final BooleanFlag USE_SEARCH_REQUEST_TIMEOUT_OVERRIDES = getDebugFlag(
+            "USE_SEARCH_REQUEST_TIMEOUT_OVERRIDES", false,
+            "Use local overrides for search request timeout");
+
     public static void initialize(Context context) {
         synchronized (sDebugFlags) {
             for (DebugFlag flag : sDebugFlags) {
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 0264ae2..09fe740 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -363,7 +363,10 @@
             // If the content is already removed, ignore
             return;
         }
-        View newContent = getViewFromDrawable(getContext(), crossFadeDrawable);
+        ImageView newContent = getViewFromDrawable(getContext(), crossFadeDrawable);
+        // We need to fill the ImageView with the content, otherwise the shapes of the final view
+        // and the drag view might not match exactly
+        newContent.setScaleType(ImageView.ScaleType.FIT_XY);
         newContent.measure(makeMeasureSpec(mWidth, EXACTLY), makeMeasureSpec(mHeight, EXACTLY));
         newContent.layout(0, 0, mWidth, mHeight);
         addViewInLayout(newContent, 0, new LayoutParams(mWidth, mHeight));
@@ -573,7 +576,7 @@
         }
     }
 
-    private static View getViewFromDrawable(Context context, Drawable drawable) {
+    private static ImageView getViewFromDrawable(Context context, Drawable drawable) {
         ImageView iv = new ImageView(context);
         iv.setImageDrawable(drawable);
         return iv;
diff --git a/src/com/android/launcher3/folder/PreviewItemManager.java b/src/com/android/launcher3/folder/PreviewItemManager.java
index 6355b62..2e5f2e5 100644
--- a/src/com/android/launcher3/folder/PreviewItemManager.java
+++ b/src/com/android/launcher3/folder/PreviewItemManager.java
@@ -79,6 +79,8 @@
     private int mPrevTopPadding = -1;
     private Drawable mReferenceDrawable = null;
 
+    private int mNumOfPrevItems = 0;
+
     // These hold the first page preview items
     private ArrayList<PreviewItemDrawingParams> mFirstPageParams = new ArrayList<>();
     // These hold the current page preview items. It is empty if the current page is the first page.
@@ -254,7 +256,6 @@
 
     void buildParamsForPage(int page, ArrayList<PreviewItemDrawingParams> params, boolean animate) {
         List<WorkspaceItemInfo> items = mIcon.getPreviewItemsOnPage(page);
-        int prevNumItems = params.size();
 
         // We adjust the size of the list to match the number of items in the preview.
         while (items.size() < params.size()) {
@@ -278,8 +279,9 @@
                     mReferenceDrawable = p.drawable;
                 }
             } else {
-                FolderPreviewItemAnim anim = new FolderPreviewItemAnim(this, p, i, prevNumItems, i,
-                        numItemsInFirstPagePreview, DROP_IN_ANIMATION_DURATION, null);
+                FolderPreviewItemAnim anim = new FolderPreviewItemAnim(this, p, i,
+                        mNumOfPrevItems, i, numItemsInFirstPagePreview, DROP_IN_ANIMATION_DURATION,
+                        null);
 
                 if (p.anim != null) {
                     if (p.anim.hasEqualFinalState(anim)) {
@@ -318,7 +320,9 @@
     }
 
     void updatePreviewItems(boolean animate) {
+        int numOfPrevItemsAux = mFirstPageParams.size();
         buildParamsForPage(0, mFirstPageParams, animate);
+        mNumOfPrevItems = numOfPrevItemsAux;
     }
 
     void updatePreviewItems(Predicate<WorkspaceItemInfo> itemCheck) {
diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
index d2e4c51..0eb86b1 100644
--- a/src/com/android/launcher3/graphics/PreloadIconDrawable.java
+++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
@@ -28,11 +28,9 @@
 import android.graphics.Path;
 import android.graphics.PathMeasure;
 import android.graphics.Rect;
-import android.util.Pair;
 import android.util.Property;
-import android.util.SparseArray;
-import android.view.ContextThemeWrapper;
 
+import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.icons.FastBitmapDrawable;
@@ -40,8 +38,6 @@
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.util.Themes;
 
-import java.lang.ref.WeakReference;
-
 /**
  * Extension of {@link FastBitmapDrawable} which shows a progress bar around the icon.
  */
@@ -61,9 +57,9 @@
             };
 
     private static final int DEFAULT_PATH_SIZE = 100;
-    private static final float PROGRESS_WIDTH = 7;
-    private static final float PROGRESS_GAP = 2;
     private static final int MAX_PAINT_ALPHA = 255;
+    private static final int TRACK_ALPHA = (int) (0.27f * MAX_PAINT_ALPHA);
+    private static final int DISABLED_ICON_ALPHA = (int) (0.6f * MAX_PAINT_ALPHA);
 
     private static final long DURATION_SCALE = 500;
 
@@ -71,13 +67,8 @@
     // Duration = COMPLETE_ANIM_FRACTION * DURATION_SCALE
     private static final float COMPLETE_ANIM_FRACTION = 0.3f;
 
-    private static final int COLOR_TRACK = 0x77EEEEEE;
-    private static final int COLOR_SHADOW = 0x55000000;
-
-    private static final float SMALL_SCALE = 0.6f;
-
-    private static final SparseArray<WeakReference<Pair<Path, Bitmap>>> sShadowCache =
-            new SparseArray<>();
+    private static final float SMALL_SCALE = 0.7f;
+    private static final float PROGRESS_STROKE_SCALE = 0.075f;
 
     private static final int PRELOAD_ACCENT_COLOR_INDEX = 0;
     private static final int PRELOAD_BACKGROUND_COLOR_INDEX = 1;
@@ -94,7 +85,6 @@
     private final Path mScaledProgressPath;
     private final Paint mProgressPaint;
 
-    private Bitmap mShadowBitmap;
     private final int mIndicatorColor;
     private final int mSystemAccentColor;
     private final int mSystemBackgroundColor;
@@ -134,7 +124,6 @@
         mScaledProgressPath = new Path();
 
         mProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
-        mProgressPaint.setStyle(Paint.Style.STROKE);
         mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
         mIndicatorColor = indicatorColor;
 
@@ -149,47 +138,22 @@
     @Override
     protected void onBoundsChange(Rect bounds) {
         super.onBoundsChange(bounds);
+
+        float progressWidth = PROGRESS_STROKE_SCALE * bounds.width();
         mTmpMatrix.setScale(
-                (bounds.width() - 2 * PROGRESS_WIDTH - 2 * PROGRESS_GAP) / DEFAULT_PATH_SIZE,
-                (bounds.height() - 2 * PROGRESS_WIDTH - 2 * PROGRESS_GAP) / DEFAULT_PATH_SIZE);
-        mTmpMatrix.postTranslate(
-                bounds.left + PROGRESS_WIDTH + PROGRESS_GAP,
-                bounds.top + PROGRESS_WIDTH + PROGRESS_GAP);
+                (bounds.width() - 2 * progressWidth) / DEFAULT_PATH_SIZE,
+                (bounds.height() - 2 * progressWidth) / DEFAULT_PATH_SIZE);
+        mTmpMatrix.postTranslate(bounds.left + progressWidth, bounds.top + progressWidth);
 
         mShapePath.transform(mTmpMatrix, mScaledTrackPath);
-        float scale = bounds.width() / DEFAULT_PATH_SIZE;
-        mProgressPaint.setStrokeWidth(PROGRESS_WIDTH * scale);
+        mProgressPaint.setStrokeWidth(progressWidth);
 
-        mShadowBitmap = getShadowBitmap(bounds.width(), bounds.height(),
-                (PROGRESS_GAP ) * scale);
         mPathMeasure.setPath(mScaledTrackPath, true);
         mTrackLength = mPathMeasure.getLength();
 
         setInternalProgress(mInternalStateProgress);
     }
 
-    private Bitmap getShadowBitmap(int width, int height, float shadowRadius) {
-        int key = ((width << 16) | height) * (mIsDarkMode ? -1 : 1);
-        WeakReference<Pair<Path, Bitmap>> shadowRef = sShadowCache.get(key);
-        Pair<Path, Bitmap> cache = shadowRef != null ? shadowRef.get() : null;
-        Bitmap shadow = cache != null && cache.first.equals(mShapePath) ? cache.second : null;
-        if (shadow != null) {
-            return shadow;
-        }
-        shadow = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
-        Canvas c = new Canvas(shadow);
-        mProgressPaint.setShadowLayer(shadowRadius, 0, 0, mIsStartable
-                ? COLOR_SHADOW : mSystemAccentColor);
-        mProgressPaint.setColor(mIsStartable ? COLOR_TRACK : mSystemBackgroundColor);
-        mProgressPaint.setAlpha(MAX_PAINT_ALPHA);
-        c.drawPath(mScaledTrackPath, mProgressPaint);
-        mProgressPaint.clearShadowLayer();
-        c.setBitmap(null);
-
-        sShadowCache.put(key, new WeakReference<>(Pair.create(mShapePath, shadow)));
-        return shadow;
-    }
-
     @Override
     public void drawInternal(Canvas canvas, Rect bounds) {
         if (mRanFinishAnimation) {
@@ -197,12 +161,17 @@
             return;
         }
 
-        // Draw track.
+        // Draw background.
+        mProgressPaint.setStyle(Paint.Style.FILL_AND_STROKE);
+        mProgressPaint.setColor(mSystemBackgroundColor);
+        canvas.drawPath(mScaledTrackPath, mProgressPaint);
+
+        // Draw track and progress.
+        mProgressPaint.setStyle(Paint.Style.STROKE);
         mProgressPaint.setColor(mIsStartable ? mIndicatorColor : mSystemAccentColor);
+        mProgressPaint.setAlpha(TRACK_ALPHA);
+        canvas.drawPath(mScaledTrackPath, mProgressPaint);
         mProgressPaint.setAlpha(mTrackAlpha);
-        if (mShadowBitmap != null) {
-            canvas.drawBitmap(mShadowBitmap, bounds.left, bounds.top, mProgressPaint);
-        }
         canvas.drawPath(mScaledProgressPath, mProgressPaint);
 
         int saveCount = canvas.save();
@@ -211,6 +180,11 @@
         canvas.restoreToCount(saveCount);
     }
 
+    @Override
+    protected void updateFilter() {
+        setAlpha(mIsDisabled ? DISABLED_ICON_ALPHA : MAX_PAINT_ALPHA);
+    }
+
     /**
      * Updates the install progress based on the level
      */
@@ -326,13 +300,12 @@
     }
 
     private static int[] getPreloadColors(Context context) {
-        Context dayNightThemeContext = new ContextThemeWrapper(
-                context, android.R.style.Theme_DeviceDefault_DayNight);
         int[] preloadColors = new int[2];
 
-        preloadColors[PRELOAD_ACCENT_COLOR_INDEX] = Themes.getColorAccent(dayNightThemeContext);
-        preloadColors[PRELOAD_BACKGROUND_COLOR_INDEX] = Themes.getColorBackgroundFloating(
-                dayNightThemeContext);
+        preloadColors[PRELOAD_ACCENT_COLOR_INDEX] = Themes.getAttrColor(context,
+                R.attr.preloadIconAccentColor);
+        preloadColors[PRELOAD_BACKGROUND_COLOR_INDEX] = Themes.getAttrColor(context,
+                R.attr.preloadIconBackgroundColor);
 
         return preloadColors;
     }
diff --git a/src/com/android/launcher3/model/DeviceGridState.java b/src/com/android/launcher3/model/DeviceGridState.java
index 35fcb78..46f0b0b 100644
--- a/src/com/android/launcher3/model/DeviceGridState.java
+++ b/src/com/android/launcher3/model/DeviceGridState.java
@@ -134,10 +134,13 @@
      * DeviceGridState without migration, or false otherwise.
      */
     public boolean isCompatible(DeviceGridState other) {
-        if (this == other) return true;
-        if (other == null) return false;
-        return mNumHotseat == other.mNumHotseat
-                && Objects.equals(mGridSizeString, other.mGridSizeString);
+        if (this == other) {
+            return true;
+        }
+        if (other == null) {
+            return false;
+        }
+        return Objects.equals(mDbFile, other.mDbFile);
     }
 
     public Integer getColumns() {
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
index ef9250c..c25929a 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
@@ -56,6 +56,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * This class takes care of shrinking the workspace (by maximum of one row and one column), as a
@@ -109,9 +110,8 @@
     private static boolean needsToMigrate(
             DeviceGridState srcDeviceState, DeviceGridState destDeviceState) {
         boolean needsToMigrate = !destDeviceState.isCompatible(srcDeviceState);
-        // TODO(b/198965093): Revert this change after bug is fixed
         if (needsToMigrate) {
-            Log.d("b/198965093", "Migration is needed. destDeviceState: " + destDeviceState
+            Log.i(TAG, "Migration is needed. destDeviceState: " + destDeviceState
                     + ", srcDeviceState: " + srcDeviceState);
         }
         return needsToMigrate;
@@ -267,38 +267,33 @@
 
     /** Return what's in the src but not in the dest */
     private static List<DbEntry> calcDiff(List<DbEntry> src, List<DbEntry> dest) {
-        Set<String> destIntentSet = new HashSet<>();
-        Set<Map<String, Integer>> destFolderIntentSet = new HashSet<>();
+        Map<String, Integer> destIdSet = new HashMap<>();
         for (DbEntry entry : dest) {
-            if (entry.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
-                destFolderIntentSet.add(getFolderIntents(entry));
+            String entryID = entry.getEntryMigrationId();
+            if (destIdSet.containsKey(entryID)) {
+                destIdSet.put(entryID, destIdSet.get(entryID) + 1);
             } else {
-                destIntentSet.add(entry.mIntent);
+                destIdSet.put(entryID, 1);
             }
         }
         List<DbEntry> diff = new ArrayList<>();
         for (DbEntry entry : src) {
-            if (entry.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
-                if (!destFolderIntentSet.contains(getFolderIntents(entry))) {
+            String entryID = entry.getEntryMigrationId();
+            if (destIdSet.containsKey(entryID)) {
+                Integer count = destIdSet.get(entryID);
+                if (count <= 0) {
                     diff.add(entry);
+                    destIdSet.remove(entryID);
+                } else {
+                    destIdSet.put(entryID, count - 1);
                 }
             } else {
-                if (!destIntentSet.contains(entry.mIntent)) {
-                    diff.add(entry);
-                }
+                diff.add(entry);
             }
         }
         return diff;
     }
 
-    private static Map<String, Integer> getFolderIntents(DbEntry entry) {
-        Map<String, Integer> folder = new HashMap<>();
-        for (String intent : entry.mFolderItems.keySet()) {
-            folder.put(intent, entry.mFolderItems.get(intent).size());
-        }
-        return folder;
-    }
-
     private static void insertEntryInDb(SQLiteDatabase db, Context context, DbEntry entry,
             String srcTableName, String destTableName) {
         int id = copyEntryAndUpdate(db, context, entry, srcTableName, destTableName);
@@ -780,5 +775,31 @@
             values.put(LauncherSettings.Favorites.SPANX, spanX);
             values.put(LauncherSettings.Favorites.SPANY, spanY);
         }
+
+        /**
+         * This method should return an id that should be the same for two folders containing the
+         * same elements.
+         */
+        private String getFolderMigrationId() {
+            return mFolderItems.keySet().stream()
+                    .map(intentString -> mFolderItems.get(intentString).size() + intentString)
+                    .sorted()
+                    .collect(Collectors.joining(","));
+        }
+
+        /** This id is not used in the DB is only used while doing the migration and it identifies
+         * an entry on each workspace. For example two calculator icons would have the same
+         * migration id even thought they have different database ids.
+         */
+        public String getEntryMigrationId() {
+            switch (itemType) {
+                case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+                    return getFolderMigrationId();
+                case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+                    return mProvider;
+                default:
+                    return mIntent;
+            }
+        }
     }
 }
diff --git a/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java b/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java
index 1681ea5..87ae890 100644
--- a/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java
+++ b/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java
@@ -271,7 +271,7 @@
         } else {
             lp.leftMargin = lp.rightMargin = 0;
             lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
-            lp.bottomMargin = grid.hotseatBarSizePx + insets.bottom;
+            lp.bottomMargin = grid.hotseatBarSizePx;
         }
         setLayoutParams(lp);
     }
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 49d97d2..8e7a10c 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -72,6 +72,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.stream.Collectors;
 
 /**
@@ -244,23 +245,32 @@
                 mNotificationContainer);
     }
 
+    private void initializeSystemShortcuts(List<SystemShortcut> shortcuts) {
+        if (shortcuts.isEmpty()) {
+            return;
+        }
+        // If there is only 1 shortcut, add it to its own container so it can show text and icon
+        if (shortcuts.size() == 1) {
+            initializeSystemShortcut(R.layout.system_shortcut, this, shortcuts.get(0));
+            return;
+        }
+        mSystemShortcutContainer = inflateAndAdd(R.layout.system_shortcut_icons, this);
+        for (SystemShortcut shortcut : shortcuts) {
+            initializeSystemShortcut(
+                    R.layout.system_shortcut_icon_only, mSystemShortcutContainer,
+                    shortcut);
+        }
+    }
+
     @TargetApi(Build.VERSION_CODES.P)
     public void populateAndShow(final BubbleTextView originalIcon, int shortcutCount,
-            final List<NotificationKeyData> notificationKeys, List<SystemShortcut> systemShortcuts) {
+            final List<NotificationKeyData> notificationKeys, List<SystemShortcut> shortcuts) {
         mNumNotifications = notificationKeys.size();
         mOriginalIcon = originalIcon;
 
         boolean hasDeepShortcuts = shortcutCount > 0;
         mContainerWidth = getResources().getDimensionPixelSize(R.dimen.bg_popup_item_width);
 
-        // if there are deep shortcuts, we might want to increase the width of shortcuts to fit
-        // horizontally laid out system shortcuts.
-        if (hasDeepShortcuts) {
-            mContainerWidth = Math.max(mContainerWidth,
-                    systemShortcuts.size() * getResources()
-                            .getDimensionPixelSize(R.dimen.system_shortcut_header_icon_touch_size)
-            );
-        }
         // Add views
         if (mNumNotifications > 0) {
             // Add notification entries
@@ -279,6 +289,24 @@
             mDeepShortcutContainer = findViewById(R.id.deep_shortcuts_container);
         }
         if (hasDeepShortcuts) {
+            // Remove the widget shortcut fom the list
+            List<SystemShortcut> systemShortcuts = shortcuts
+                    .stream()
+                    .filter(shortcut -> !(shortcut instanceof SystemShortcut.Widgets))
+                    .collect(Collectors.toList());
+            Optional<SystemShortcut.Widgets> widgetShortcutOpt = shortcuts
+                    .stream()
+                    .filter(shortcut -> shortcut instanceof SystemShortcut.Widgets)
+                    .map(SystemShortcut.Widgets.class::cast)
+                    .findFirst();
+
+            // if there are deep shortcuts, we might want to increase the width of shortcuts to fit
+            // horizontally laid out system shortcuts.
+            mContainerWidth = Math.max(mContainerWidth,
+                    systemShortcuts.size() * getResources()
+                            .getDimensionPixelSize(R.dimen.system_shortcut_header_icon_touch_size)
+            );
+
             mDeepShortcutContainer.setVisibility(View.VISIBLE);
 
             for (int i = shortcutCount; i > 0; i--) {
@@ -288,30 +316,19 @@
             }
             updateHiddenShortcuts();
 
-            if (!systemShortcuts.isEmpty()) {
-                for (SystemShortcut shortcut : systemShortcuts) {
-                    if (shortcut instanceof SystemShortcut.Widgets) {
-                        if (mWidgetContainer == null) {
-                            mWidgetContainer = inflateAndAdd(R.layout.widget_shortcut_container,
-                                    this);
-                        }
-                        initializeWidgetShortcut(mWidgetContainer, shortcut);
-                    }
+            if (widgetShortcutOpt.isPresent()) {
+                if (mWidgetContainer == null) {
+                    mWidgetContainer = inflateAndAdd(R.layout.widget_shortcut_container,
+                            this);
                 }
-                mSystemShortcutContainer = inflateAndAdd(R.layout.system_shortcut_icons, this);
-
-                for (SystemShortcut shortcut : systemShortcuts) {
-                    if (!(shortcut instanceof SystemShortcut.Widgets)) {
-                        initializeSystemShortcut(
-                                R.layout.system_shortcut_icon_only, mSystemShortcutContainer,
-                                shortcut);
-                    }
-                }
+                initializeWidgetShortcut(mWidgetContainer, widgetShortcutOpt.get());
             }
+
+            initializeSystemShortcuts(systemShortcuts);
         } else {
             mDeepShortcutContainer.setVisibility(View.GONE);
-            if (!systemShortcuts.isEmpty()) {
-                for (SystemShortcut shortcut : systemShortcuts) {
+            if (!shortcuts.isEmpty()) {
+                for (SystemShortcut shortcut : shortcuts) {
                     initializeSystemShortcut(R.layout.system_shortcut, this, shortcut);
                 }
             }
diff --git a/src/com/android/launcher3/statemanager/StatefulActivity.java b/src/com/android/launcher3/statemanager/StatefulActivity.java
index c554d06..2158dea 100644
--- a/src/com/android/launcher3/statemanager/StatefulActivity.java
+++ b/src/com/android/launcher3/statemanager/StatefulActivity.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.statemanager;
 
+import static com.android.launcher3.LauncherState.FLAG_CLOSE_POPUPS;
 import static com.android.launcher3.LauncherState.FLAG_NON_INTERACTIVE;
 
 import android.os.Handler;
@@ -23,6 +24,7 @@
 
 import androidx.annotation.CallSuper;
 
+import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.LauncherRootView;
 import com.android.launcher3.Utilities;
@@ -87,6 +89,10 @@
         if (mDeferredResumePending) {
             handleDeferredResume();
         }
+
+        if (state.hasFlag(FLAG_CLOSE_POPUPS)) {
+            AbstractFloatingView.closeAllOpenViews(this, !state.hasFlag(FLAG_NON_INTERACTIVE));
+        }
     }
 
     /**
diff --git a/src/com/android/launcher3/testing/HotseatCellCenterRequest.java b/src/com/android/launcher3/testing/HotseatCellCenterRequest.java
new file mode 100644
index 0000000..cbb847e
--- /dev/null
+++ b/src/com/android/launcher3/testing/HotseatCellCenterRequest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.testing;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Request object for querying a hotseat cell region in Rect.
+ */
+public class HotseatCellCenterRequest implements TestInformationRequest {
+    public final int cellInd;
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(cellInd);
+    }
+
+    @Override
+    public String getRequestName() {
+        return TestProtocol.REQUEST_HOTSEAT_CELL_CENTER;
+    }
+
+    public static final Parcelable.Creator<HotseatCellCenterRequest> CREATOR =
+            new Parcelable.Creator<HotseatCellCenterRequest>() {
+
+                @Override
+                public HotseatCellCenterRequest createFromParcel(Parcel source) {
+                    return new HotseatCellCenterRequest(source);
+                }
+
+                @Override
+                public HotseatCellCenterRequest[] newArray(int size) {
+                    return new HotseatCellCenterRequest[size];
+                }
+            };
+
+    private HotseatCellCenterRequest(int cellInd) {
+        this.cellInd = cellInd;
+    }
+
+    private HotseatCellCenterRequest(Parcel in) {
+        this(in.readInt());
+    }
+
+    /**
+     * Create a builder for HotseatCellCenterRequest.
+     *
+     * @return HotseatCellCenterRequest builder.
+     */
+    public static HotseatCellCenterRequest.Builder builder() {
+        return new HotseatCellCenterRequest.Builder();
+    }
+
+    /**
+     * HotseatCellCenterRequest Builder.
+     */
+    public static final class Builder {
+        private int mCellInd;
+
+        private Builder() {
+            mCellInd = 0;
+        }
+
+        /**
+         * Set the index of hotseat cells.
+         */
+        public HotseatCellCenterRequest.Builder setCellInd(int i) {
+            this.mCellInd = i;
+            return this;
+        }
+
+        /**
+         * build the HotseatCellCenterRequest.
+         */
+        public HotseatCellCenterRequest build() {
+            return new HotseatCellCenterRequest(mCellInd);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index 242d2d4..7f444d6 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -33,6 +33,7 @@
 
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Hotseat;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
@@ -153,10 +154,6 @@
                         mDeviceProfile.isTwoPanels);
                 return response;
 
-            case TestProtocol.REQUEST_SET_FORCE_PAUSE_TIMEOUT:
-                TestProtocol.sForcePauseTimeout = Long.parseLong(arg);
-                return response;
-
             case TestProtocol.REQUEST_GET_HAD_NONTEST_EVENTS:
                 response.putBoolean(
                         TestProtocol.TEST_INFO_RESPONSE_FIELD, TestLogging.sHadEventsNotFromTest);
@@ -185,7 +182,7 @@
                     return new int[]{cellLayout.getCountX(), cellLayout.getCountY()};
                 });
 
-            case TestProtocol.REQUEST_WORKSPACE_CELL_CENTER:
+            case TestProtocol.REQUEST_WORKSPACE_CELL_CENTER: {
                 final WorkspaceCellCenterRequest request = extra.getParcelable(
                         TestProtocol.TEST_INFO_REQUEST_FIELD);
                 return getLauncherUIProperty(Bundle::putParcelable, launcher -> {
@@ -197,6 +194,21 @@
                             cellLayout, request.cellX, request.cellY, request.spanX, request.spanY);
                     return new Point(cellRect.centerX(), cellRect.centerY());
                 });
+            }
+
+            case TestProtocol.REQUEST_HOTSEAT_CELL_CENTER: {
+                final HotseatCellCenterRequest request = extra.getParcelable(
+                        TestProtocol.TEST_INFO_REQUEST_FIELD);
+                return getLauncherUIProperty(Bundle::putParcelable, launcher -> {
+                    final Hotseat hotseat = launcher.getHotseat();
+                    final Rect cellRect = getDescendantRectRelativeToDragLayerForCell(launcher,
+                            hotseat, request.cellInd, /* cellY= */ 0,
+                            /* spanX= */ 1, /* spanY= */ 1);
+                    // TODO(b/234322284): return the real center point.
+                    return new Point(cellRect.left + (cellRect.right - cellRect.left) / 3,
+                            cellRect.centerY());
+                });
+            }
 
             case TestProtocol.REQUEST_HAS_TIS: {
                 response.putBoolean(
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 3a030a8..9bc9067 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -115,6 +115,8 @@
     public static final String REQUEST_WORKSPACE_CELL_LAYOUT_SIZE = "workspace-cell-layout-size";
     public static final String REQUEST_WORKSPACE_CELL_CENTER = "workspace-cell-center";
 
+    public static final String REQUEST_HOTSEAT_CELL_CENTER = "hotseat-cell-center";
+
     public static final String REQUEST_GET_FOCUSED_TASK_HEIGHT_FOR_TABLET =
             "get-focused-task-height-for-tablet";
     public static final String REQUEST_GET_GRID_TASK_SIZE_RECT_FOR_TABLET =
@@ -122,9 +124,6 @@
     public static final String REQUEST_GET_OVERVIEW_PAGE_SPACING = "get-overview-page-spacing";
     public static final String REQUEST_ENABLE_ROTATION = "enable_rotation";
 
-    public static Long sForcePauseTimeout;
-    public static final String REQUEST_SET_FORCE_PAUSE_TIMEOUT = "set-force-pause-timeout";
-
     public static boolean sDebugTracing = false;
     public static final String REQUEST_ENABLE_DEBUG_TRACING = "enable-debug-tracing";
     public static final String REQUEST_DISABLE_DEBUG_TRACING = "disable-debug-tracing";
@@ -139,4 +138,9 @@
     public static final String MISSING_PROMISE_ICON = "b/202985412";
     public static final String BAD_STATE = "b/223498680";
     public static final String TASKBAR_IN_APP_STATE = "b/227657604";
+
+    public static final String REQUEST_EMULATE_DISPLAY = "emulate-display";
+    public static final String REQUEST_STOP_EMULATE_DISPLAY = "stop-emulate-display";
+    public static final String REQUEST_IS_EMULATE_DISPLAY_RUNNING = "is-emulate-display-running";
+    public static final String REQUEST_EMULATE_PRINT_DEVICE = "emulate-print-device";
 }
diff --git a/src/com/android/launcher3/touch/AllAppsSwipeController.java b/src/com/android/launcher3/touch/AllAppsSwipeController.java
index db43baa..37b76fb 100644
--- a/src/com/android/launcher3/touch/AllAppsSwipeController.java
+++ b/src/com/android/launcher3/touch/AllAppsSwipeController.java
@@ -47,48 +47,88 @@
  */
 public class AllAppsSwipeController extends AbstractStateChangeTouchController {
 
-    private static final float ALLAPPS_STAGGERED_FADE_THRESHOLD = 0.5f;
+    private static final float ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD = 0.8f;
+    private static final float ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD = 0.5f;
+    private static final float ALL_APPS_SCRIM_VISIBLE_THRESHOLD = 0.1f;
+    private static final float ALL_APPS_STAGGERED_FADE_THRESHOLD = 0.5f;
 
-    // Custom timing for NORMAL -> ALL_APPS on phones only.
-    private static final float WORKSPACE_MOTION_START = 0.1667f;
-    private static final float ALL_APPS_STATE_TRANSITION = 0.305f;
-    private static final float ALL_APPS_FADE_END = 0.4717f;
+    public static final Interpolator ALL_APPS_SCRIM_RESPONDER =
+            Interpolators.clampToProgress(
+                    LINEAR, ALL_APPS_SCRIM_VISIBLE_THRESHOLD, ALL_APPS_STAGGERED_FADE_THRESHOLD);
+    public static final Interpolator ALL_APPS_CLAMPING_RESPONDER =
+            Interpolators.clampToProgress(
+                    LINEAR,
+                    1 - ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD,
+                    1 - ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD);
+
+    // ---- Custom interpolators for NORMAL -> ALL_APPS on phones only. ----
+
+    private static final float WORKSPACE_MOTION_START_ATOMIC = 0.1667f;
+    private static final float ALL_APPS_STATE_TRANSITION_ATOMIC = 0.305f;
+    private static final float ALL_APPS_STATE_TRANSITION_MANUAL = 0.4f;
+    private static final float ALL_APPS_FADE_END_ATOMIC = 0.4717f;
     private static final float ALL_APPS_FULL_DEPTH_PROGRESS = 0.5f;
 
-    public static final Interpolator ALLAPPS_STAGGERED_FADE_EARLY_RESPONDER =
-            Interpolators.clampToProgress(LINEAR, 0, ALLAPPS_STAGGERED_FADE_THRESHOLD);
-    public static final Interpolator ALLAPPS_STAGGERED_FADE_LATE_RESPONDER =
-            Interpolators.clampToProgress(LINEAR, ALLAPPS_STAGGERED_FADE_THRESHOLD, 1f);
+    private static final Interpolator LINEAR_EARLY_MANUAL =
+            Interpolators.clampToProgress(LINEAR, 0f, ALL_APPS_STATE_TRANSITION_MANUAL);
+    private static final Interpolator STEP_TRANSITION_ATOMIC =
+            Interpolators.clampToProgress(FINAL_FRAME, 0f, ALL_APPS_STATE_TRANSITION_ATOMIC);
+    private static final Interpolator STEP_TRANSITION_MANUAL =
+            Interpolators.clampToProgress(FINAL_FRAME, 0f, ALL_APPS_STATE_TRANSITION_MANUAL);
 
-    // Custom interpolators for NORMAL -> ALL_APPS on phones only.
     // The blur to All Apps is set to be complete when the interpolator is at 0.5.
-    public static final Interpolator BLUR =
+    private static final Interpolator BLUR_ADJUSTED =
+            Interpolators.mapToProgress(LINEAR, 0f, ALL_APPS_FULL_DEPTH_PROGRESS);
+    public static final Interpolator BLUR_ATOMIC =
             Interpolators.clampToProgress(
-                    Interpolators.mapToProgress(
-                            LINEAR, 0f, ALL_APPS_FULL_DEPTH_PROGRESS),
-                    WORKSPACE_MOTION_START, ALL_APPS_STATE_TRANSITION);
-    public static final Interpolator WORKSPACE_FADE =
-            Interpolators.clampToProgress(FINAL_FRAME, 0f, ALL_APPS_STATE_TRANSITION);
-    public static final Interpolator WORKSPACE_SCALE =
+                    BLUR_ADJUSTED, WORKSPACE_MOTION_START_ATOMIC, ALL_APPS_STATE_TRANSITION_ATOMIC);
+    public static final Interpolator BLUR_MANUAL =
+            Interpolators.clampToProgress(BLUR_ADJUSTED, 0f, ALL_APPS_STATE_TRANSITION_MANUAL);
+
+    public static final Interpolator WORKSPACE_FADE_ATOMIC = STEP_TRANSITION_ATOMIC;
+    public static final Interpolator WORKSPACE_FADE_MANUAL = STEP_TRANSITION_MANUAL;
+
+    public static final Interpolator WORKSPACE_SCALE_ATOMIC =
             Interpolators.clampToProgress(
-                    EMPHASIZED_ACCELERATE, WORKSPACE_MOTION_START, ALL_APPS_STATE_TRANSITION);
-    public static final Interpolator HOTSEAT_FADE = WORKSPACE_FADE;
-    public static final Interpolator HOTSEAT_SCALE = HOTSEAT_FADE;
-    public static final Interpolator HOTSEAT_TRANSLATE =
+                    EMPHASIZED_ACCELERATE, WORKSPACE_MOTION_START_ATOMIC,
+                    ALL_APPS_STATE_TRANSITION_ATOMIC);
+    public static final Interpolator WORKSPACE_SCALE_MANUAL = LINEAR_EARLY_MANUAL;
+
+    public static final Interpolator HOTSEAT_FADE_ATOMIC = STEP_TRANSITION_ATOMIC;
+    public static final Interpolator HOTSEAT_FADE_MANUAL = STEP_TRANSITION_MANUAL;
+
+    public static final Interpolator HOTSEAT_SCALE_ATOMIC = STEP_TRANSITION_ATOMIC;
+    public static final Interpolator HOTSEAT_SCALE_MANUAL = LINEAR_EARLY_MANUAL;
+
+    public static final Interpolator HOTSEAT_TRANSLATE_ATOMIC =
             Interpolators.clampToProgress(
-                    EMPHASIZED_ACCELERATE, WORKSPACE_MOTION_START, ALL_APPS_STATE_TRANSITION);
-    public static final Interpolator SCRIM_FADE =
+                    EMPHASIZED_ACCELERATE, WORKSPACE_MOTION_START_ATOMIC,
+                    ALL_APPS_STATE_TRANSITION_ATOMIC);
+    public static final Interpolator HOTSEAT_TRANSLATE_MANUAL = STEP_TRANSITION_MANUAL;
+
+    public static final Interpolator SCRIM_FADE_ATOMIC =
             Interpolators.clampToProgress(
                     Interpolators.mapToProgress(LINEAR, 0f, 0.8f),
-                    WORKSPACE_MOTION_START, ALL_APPS_STATE_TRANSITION);
-    public static final Interpolator ALL_APPS_FADE =
+                    WORKSPACE_MOTION_START_ATOMIC, ALL_APPS_STATE_TRANSITION_ATOMIC);
+    public static final Interpolator SCRIM_FADE_MANUAL = LINEAR_EARLY_MANUAL;
+
+    public static final Interpolator ALL_APPS_FADE_ATOMIC =
             Interpolators.clampToProgress(
-                    Interpolators.mapToProgress(DECELERATED_EASE, 0.2f, 1.0f),
-                    ALL_APPS_STATE_TRANSITION, ALL_APPS_FADE_END);
-    public static final Interpolator ALL_APPS_VERTICAL_PROGRESS =
+                    Interpolators.mapToProgress(DECELERATED_EASE, 0.2f, 1f),
+                    ALL_APPS_STATE_TRANSITION_ATOMIC, ALL_APPS_FADE_END_ATOMIC);
+    public static final Interpolator ALL_APPS_FADE_MANUAL =
+            Interpolators.clampToProgress(LINEAR, ALL_APPS_STATE_TRANSITION_MANUAL, 1f);
+
+    public static final Interpolator ALL_APPS_VERTICAL_PROGRESS_ATOMIC =
             Interpolators.clampToProgress(
-                    Interpolators.mapToProgress(EMPHASIZED_DECELERATE, 0.4f, 1.0f),
-                    ALL_APPS_STATE_TRANSITION, 1.0f);
+                    Interpolators.mapToProgress(EMPHASIZED_DECELERATE, 0.4f, 1f),
+                    ALL_APPS_STATE_TRANSITION_ATOMIC, 1f);
+    public static final Interpolator ALL_APPS_VERTICAL_PROGRESS_MANUAL =
+            Interpolators.clampToProgress(
+                    Interpolators.mapToProgress(LINEAR, ALL_APPS_STATE_TRANSITION_MANUAL, 1f),
+                    ALL_APPS_STATE_TRANSITION_MANUAL, 1f);
+
+    // --------
 
     public AllAppsSwipeController(Launcher l) {
         super(l, SingleAxisSwipeDetector.VERTICAL);
@@ -141,6 +181,7 @@
     protected StateAnimationConfig getConfigForStates(LauncherState fromState,
             LauncherState toState) {
         StateAnimationConfig config = super.getConfigForStates(fromState, toState);
+        config.userControlled = true;
         if (fromState == NORMAL && toState == ALL_APPS) {
             applyNormalToAllAppsAnimConfig(mLauncher, config);
         } else if (fromState == ALL_APPS && toState == NORMAL) {
@@ -150,36 +191,75 @@
     }
 
     /**
-     * Applies Animation config values for transition from all apps to home
+     * Applies Animation config values for transition from all apps to home.
      */
     public static void applyAllAppsToNormalConfig(Launcher launcher, StateAnimationConfig config) {
-        boolean isTablet = launcher.getDeviceProfile().isTablet;
-        config.setInterpolator(ANIM_SCRIM_FADE, ALLAPPS_STAGGERED_FADE_LATE_RESPONDER);
-        config.setInterpolator(ANIM_ALL_APPS_FADE, isTablet
-                ? FINAL_FRAME : ALLAPPS_STAGGERED_FADE_EARLY_RESPONDER);
-        if (!isTablet) {
-            config.setInterpolator(ANIM_WORKSPACE_FADE, INSTANT);
+        if (launcher.getDeviceProfile().isTablet) {
+            config.setInterpolator(ANIM_SCRIM_FADE,
+                    Interpolators.reverse(ALL_APPS_SCRIM_RESPONDER));
+            config.setInterpolator(ANIM_ALL_APPS_FADE, FINAL_FRAME);
+            if (!config.userControlled) {
+                config.setInterpolator(ANIM_VERTICAL_PROGRESS, EMPHASIZED_ACCELERATE);
+            }
+        } else {
+            if (config.userControlled) {
+                config.setInterpolator(ANIM_DEPTH, Interpolators.reverse(BLUR_MANUAL));
+                config.setInterpolator(ANIM_WORKSPACE_FADE,
+                        Interpolators.reverse(WORKSPACE_FADE_MANUAL));
+                config.setInterpolator(ANIM_WORKSPACE_SCALE,
+                        Interpolators.reverse(WORKSPACE_SCALE_MANUAL));
+                config.setInterpolator(ANIM_HOTSEAT_FADE,
+                        Interpolators.reverse(HOTSEAT_FADE_MANUAL));
+                config.setInterpolator(ANIM_HOTSEAT_SCALE,
+                        Interpolators.reverse(HOTSEAT_SCALE_MANUAL));
+                config.setInterpolator(ANIM_HOTSEAT_TRANSLATE,
+                        Interpolators.reverse(HOTSEAT_TRANSLATE_MANUAL));
+                config.setInterpolator(ANIM_SCRIM_FADE, Interpolators.reverse(SCRIM_FADE_MANUAL));
+                config.setInterpolator(ANIM_ALL_APPS_FADE,
+                        Interpolators.reverse(ALL_APPS_FADE_MANUAL));
+                config.setInterpolator(ANIM_VERTICAL_PROGRESS,
+                        Interpolators.reverse(ALL_APPS_VERTICAL_PROGRESS_MANUAL));
+            } else {
+                config.setInterpolator(ANIM_SCRIM_FADE,
+                        Interpolators.reverse(ALL_APPS_SCRIM_RESPONDER));
+                config.setInterpolator(ANIM_ALL_APPS_FADE, ALL_APPS_CLAMPING_RESPONDER);
+                config.setInterpolator(ANIM_WORKSPACE_FADE, INSTANT);
+                config.setInterpolator(ANIM_VERTICAL_PROGRESS, EMPHASIZED_ACCELERATE);
+            }
         }
     }
 
     /**
-     * Applies Animation config values for transition from home to all apps
+     * Applies Animation config values for transition from home to all apps.
      */
-    public static void applyNormalToAllAppsAnimConfig(Launcher launcher,
-            StateAnimationConfig config) {
+    public static void applyNormalToAllAppsAnimConfig(
+            Launcher launcher, StateAnimationConfig config) {
         if (launcher.getDeviceProfile().isTablet) {
-            config.setInterpolator(ANIM_SCRIM_FADE, ALLAPPS_STAGGERED_FADE_EARLY_RESPONDER);
             config.setInterpolator(ANIM_ALL_APPS_FADE, INSTANT);
+            config.setInterpolator(ANIM_SCRIM_FADE, ALL_APPS_SCRIM_RESPONDER);
+            if (!config.userControlled) {
+                config.setInterpolator(ANIM_VERTICAL_PROGRESS, EMPHASIZED_DECELERATE);
+            }
         } else {
-            config.setInterpolator(ANIM_DEPTH, BLUR);
-            config.setInterpolator(ANIM_WORKSPACE_FADE, WORKSPACE_FADE);
-            config.setInterpolator(ANIM_WORKSPACE_SCALE, WORKSPACE_SCALE);
-            config.setInterpolator(ANIM_HOTSEAT_FADE, HOTSEAT_FADE);
-            config.setInterpolator(ANIM_HOTSEAT_SCALE, HOTSEAT_SCALE);
-            config.setInterpolator(ANIM_HOTSEAT_TRANSLATE, HOTSEAT_TRANSLATE);
-            config.setInterpolator(ANIM_SCRIM_FADE, SCRIM_FADE);
-            config.setInterpolator(ANIM_ALL_APPS_FADE, ALL_APPS_FADE);
-            config.setInterpolator(ANIM_VERTICAL_PROGRESS, ALL_APPS_VERTICAL_PROGRESS);
+            config.setInterpolator(ANIM_DEPTH, config.userControlled ? BLUR_MANUAL : BLUR_ATOMIC);
+            config.setInterpolator(ANIM_WORKSPACE_FADE,
+                    config.userControlled ? WORKSPACE_FADE_MANUAL : WORKSPACE_FADE_ATOMIC);
+            config.setInterpolator(ANIM_WORKSPACE_SCALE,
+                    config.userControlled ? WORKSPACE_SCALE_MANUAL : WORKSPACE_SCALE_ATOMIC);
+            config.setInterpolator(ANIM_HOTSEAT_FADE,
+                    config.userControlled ? HOTSEAT_FADE_MANUAL : HOTSEAT_FADE_ATOMIC);
+            config.setInterpolator(ANIM_HOTSEAT_SCALE,
+                    config.userControlled ? HOTSEAT_SCALE_MANUAL : HOTSEAT_SCALE_ATOMIC);
+            config.setInterpolator(ANIM_HOTSEAT_TRANSLATE,
+                    config.userControlled ? HOTSEAT_TRANSLATE_MANUAL : HOTSEAT_TRANSLATE_ATOMIC);
+            config.setInterpolator(ANIM_SCRIM_FADE,
+                    config.userControlled ? SCRIM_FADE_MANUAL : SCRIM_FADE_ATOMIC);
+            config.setInterpolator(ANIM_ALL_APPS_FADE,
+                    config.userControlled ? ALL_APPS_FADE_MANUAL : ALL_APPS_FADE_ATOMIC);
+            config.setInterpolator(ANIM_VERTICAL_PROGRESS,
+                    config.userControlled
+                            ? ALL_APPS_VERTICAL_PROGRESS_MANUAL
+                            : ALL_APPS_VERTICAL_PROGRESS_ATOMIC);
         }
     }
 }
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index b477905..fa4eb70 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -19,6 +19,7 @@
 import static android.view.Gravity.BOTTOM;
 import static android.view.Gravity.CENTER_VERTICAL;
 import static android.view.Gravity.END;
+import static android.view.Gravity.LEFT;
 import static android.view.Gravity.START;
 import static android.view.Gravity.TOP;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
@@ -50,9 +51,9 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.SplitConfigurationOptions;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
 import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
-import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds;
 import com.android.launcher3.views.BaseDragLayer;
 
 import java.util.Collections;
@@ -311,7 +312,7 @@
 
     @Override
     public Pair<Float, Float> getDwbLayoutTranslations(int taskViewWidth,
-            int taskViewHeight, StagedSplitBounds splitBounds, DeviceProfile deviceProfile,
+            int taskViewHeight, SplitBounds splitBounds, DeviceProfile deviceProfile,
             View[] thumbnailViews, int desiredTaskId, View banner) {
         boolean isRtl = banner.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
         float translationX = 0;
@@ -424,7 +425,7 @@
     }
 
     @Override
-    public void updateStagedSplitIconParams(View out, float onScreenRectCenterX,
+    public void updateSplitIconParams(View out, float onScreenRectCenterX,
             float onScreenRectCenterY, float fullscreenScaleX, float fullscreenScaleY,
             int drawableWidth, int drawableHeight, DeviceProfile dp,
             @StagePosition int stagePosition) {
@@ -436,6 +437,28 @@
     }
 
     @Override
+    public void setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight,
+            int splitInstructionsWidth, int threeButtonNavShift) {
+        out.setPivotX(0);
+        out.setPivotY(0);
+        out.setRotation(getDegreesRotated());
+        int distanceToEdge = out.getResources().getDimensionPixelSize(
+                R.dimen.split_instructions_bottom_margin_phone_landscape);
+        // Adjust for any insets on the left edge
+        int insetCorrectionX = dp.getInsets().left;
+        // Center the view in case of unbalanced insets on top or bottom of screen
+        int insetCorrectionY = (dp.getInsets().bottom - dp.getInsets().top) / 2;
+        out.setTranslationX(splitInstructionsHeight + distanceToEdge - insetCorrectionX);
+        out.setTranslationY(((splitInstructionsHeight - splitInstructionsWidth) / 2f)
+                + insetCorrectionY);
+        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) out.getLayoutParams();
+        // Setting gravity to LEFT instead of the lint-recommended START because we always want this
+        // view to be screen-left when phone is in landscape, regardless of the RtL setting.
+        lp.gravity = LEFT | CENTER_VERTICAL;
+        out.setLayoutParams(lp);
+    }
+
+    @Override
     public void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp,
             @StagePosition int stagePosition, Rect out1, Rect out2) {
         // In fake land/seascape, the window bounds are always top and bottom half
@@ -447,7 +470,7 @@
 
     @Override
     public void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect,
-            StagedSplitBounds splitInfo, int desiredStagePosition) {
+            SplitBounds splitInfo, int desiredStagePosition) {
         float topLeftTaskPercent = splitInfo.appsStackedVertically
                 ? splitInfo.topTaskPercent
                 : splitInfo.leftTaskPercent;
@@ -464,7 +487,7 @@
 
     @Override
     public void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot,
-            int parentWidth, int parentHeight, StagedSplitBounds splitBoundsConfig,
+            int parentWidth, int parentHeight, SplitBounds splitBoundsConfig,
             DeviceProfile dp, boolean isRtl) {
         int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
         int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
@@ -506,7 +529,7 @@
     public void setSplitIconParams(View primaryIconView, View secondaryIconView,
             int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight,
             int groupedTaskViewHeight, int groupedTaskViewWidth, boolean isRtl,
-            DeviceProfile deviceProfile, StagedSplitBounds splitConfig) {
+            DeviceProfile deviceProfile, SplitBounds splitConfig) {
         FrameLayout.LayoutParams primaryIconParams =
                 (FrameLayout.LayoutParams) primaryIconView.getLayoutParams();
         FrameLayout.LayoutParams secondaryIconParams =
@@ -515,7 +538,8 @@
         // We calculate the "midpoint" of the thumbnail area, and place the icons there.
         // This is the place where the thumbnail area splits by default, in a near-50/50 split.
         // It is usually not exactly 50/50, due to insets/screen cutouts.
-        int fullscreenInsetThickness = deviceProfile.getInsets().top;
+        int fullscreenInsetThickness = deviceProfile.getInsets().top
+                - deviceProfile.getInsets().bottom;
         int fullscreenMidpointFromBottom = ((deviceProfile.heightPx - fullscreenInsetThickness)
                 / 2);
         float midpointFromBottomPct = (float) fullscreenMidpointFromBottom / deviceProfile.heightPx;
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index ca46aa8..6bc021b 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -36,7 +36,7 @@
 import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
 import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
-import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
 
 import java.util.List;
 
@@ -129,12 +129,22 @@
      * @param dp The device profile, used to report rotation and hardware insets.
      * @param stagePosition 0 if the staging area is pinned to top/left, 1 for bottom/right.
      */
-    void updateStagedSplitIconParams(View out, float onScreenRectCenterX,
+    void updateSplitIconParams(View out, float onScreenRectCenterX,
             float onScreenRectCenterY, float fullscreenScaleX, float fullscreenScaleY,
             int drawableWidth, int drawableHeight, DeviceProfile dp,
             @StagePosition int stagePosition);
 
     /**
+     * Sets positioning and rotation for a SplitInstructionsView.
+     * @param out The SplitInstructionsView that needs to be positioned.
+     * @param dp The device profile, used to report rotation and device type.
+     * @param splitInstructionsHeight The SplitInstructionView's height.
+     * @param splitInstructionsWidth  The SplitInstructionView's width.
+     */
+    void setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight,
+            int splitInstructionsWidth, int threeButtonNavShift);
+
+    /**
      * @param splitDividerSize height of split screen drag handle in portrait, width in landscape
      * @param stagePosition the split position option (top/left, bottom/right) of the first
      *                           task selected for entering split
@@ -153,12 +163,12 @@
      * @param desiredStagePosition Which stage position (topLeft/rightBottom) we want to resize
      *                           outRect for
      */
-    void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect, StagedSplitBounds splitInfo,
+    void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect, SplitBounds splitInfo,
             @SplitConfigurationOptions.StagePosition int desiredStagePosition);
 
     void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot,
             int parentWidth, int parentHeight,
-            StagedSplitBounds splitBoundsConfig, DeviceProfile dp, boolean isRtl);
+            SplitBounds splitBoundsConfig, DeviceProfile dp, boolean isRtl);
 
     // Overview TaskMenuView methods
     void setTaskIconParams(FrameLayout.LayoutParams iconParams,
@@ -166,7 +176,7 @@
     void setSplitIconParams(View primaryIconView, View secondaryIconView,
             int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight,
             int groupedTaskViewHeight, int groupedTaskViewWidth, boolean isRtl,
-            DeviceProfile deviceProfile, StagedSplitBounds splitConfig);
+            DeviceProfile deviceProfile, SplitBounds splitConfig);
 
     /*
      * The following two methods try to center the TaskMenuView in landscape by finding the center
@@ -207,7 +217,7 @@
      * @return A Pair of Floats representing the proper x and y translations.
      */
     Pair<Float, Float> getDwbLayoutTranslations(int taskViewWidth,
-            int taskViewHeight, StagedSplitBounds splitBounds, DeviceProfile deviceProfile,
+            int taskViewHeight, SplitBounds splitBounds, DeviceProfile deviceProfile,
             View[] thumbnailViews, int desiredTaskId, View banner);
 
     // The following are only used by TaskViewTouchHandler.
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index 508823c..181dca5 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -27,6 +27,7 @@
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
 import static com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL;
+import static com.android.launcher3.util.DisplayController.NavigationMode.THREE_BUTTONS;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
 
@@ -47,11 +48,13 @@
 import android.widget.LinearLayout;
 
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
 import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
-import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
 import com.android.launcher3.views.BaseDragLayer;
 
 import java.util.List;
@@ -314,7 +317,7 @@
 
     @Override
     public Pair<Float, Float> getDwbLayoutTranslations(int taskViewWidth,
-            int taskViewHeight, StagedSplitBounds splitBounds, DeviceProfile deviceProfile,
+            int taskViewHeight, SplitBounds splitBounds, DeviceProfile deviceProfile,
             View[] thumbnailViews, int desiredTaskId, View banner) {
         float translationX = 0;
         float translationY = 0;
@@ -467,7 +470,7 @@
     }
 
     @Override
-    public void updateStagedSplitIconParams(View out, float onScreenRectCenterX,
+    public void updateSplitIconParams(View out, float onScreenRectCenterX,
             float onScreenRectCenterY, float fullscreenScaleX, float fullscreenScaleY,
             int drawableWidth, int drawableHeight, DeviceProfile dp,
             @StagePosition int stagePosition) {
@@ -494,6 +497,58 @@
     }
 
     @Override
+    public void setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight,
+            int splitInstructionsWidth, int threeButtonNavShift) {
+        out.setPivotX(0);
+        out.setPivotY(0);
+        out.setRotation(getDegreesRotated());
+        int distanceToEdge;
+        if ((DisplayController.getNavigationMode(out.getContext()) == THREE_BUTTONS)
+                && (dp.isTwoPanels || dp.isTablet)) {
+            // If 3-button nav is active, align the splitInstructionsView with it.
+            distanceToEdge = dp.getTaskbarOffsetY()
+                    + ((dp.taskbarSize - splitInstructionsHeight) / 2);
+        } else {
+            // If 3-button nav is not active, set bottom margin according to spec.
+            if (dp.isPhone) {
+                if (dp.isLandscape) {
+                    distanceToEdge = out.getResources().getDimensionPixelSize(
+                            R.dimen.split_instructions_bottom_margin_phone_landscape);
+                } else {
+                    distanceToEdge = out.getResources().getDimensionPixelSize(
+                            R.dimen.split_instructions_bottom_margin_phone_portrait);
+                }
+            } else if (dp.isTwoPanels) {
+                if (dp.isLandscape) {
+                    distanceToEdge = out.getResources().getDimensionPixelSize(
+                            R.dimen.split_instructions_bottom_margin_twopanels_landscape);
+                } else {
+                    distanceToEdge = out.getResources().getDimensionPixelSize(
+                            R.dimen.split_instructions_bottom_margin_twopanels_portrait);
+                }
+            } else {
+                if (dp.isLandscape) {
+                    distanceToEdge = out.getResources().getDimensionPixelSize(
+                            R.dimen.split_instructions_bottom_margin_tablet_landscape);
+                } else {
+                    distanceToEdge = out.getResources().getDimensionPixelSize(
+                            R.dimen.split_instructions_bottom_margin_tablet_portrait);
+                }
+            }
+        }
+
+        // Center the view in case of unbalanced insets on left or right of screen
+        int insetCorrectionX = (dp.getInsets().right - dp.getInsets().left) / 2;
+        // Adjust for any insets on the bottom edge
+        int insetCorrectionY = dp.getInsets().bottom;
+        out.setTranslationX(insetCorrectionX + threeButtonNavShift);
+        out.setTranslationY(-distanceToEdge + insetCorrectionY);
+        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) out.getLayoutParams();
+        lp.gravity = CENTER_HORIZONTAL | BOTTOM;
+        out.setLayoutParams(lp);
+    }
+
+    @Override
     public void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp,
             @StagePosition int stagePosition, Rect out1, Rect out2) {
         int screenHeight = dp.heightPx;
@@ -525,7 +580,7 @@
 
     @Override
     public void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect,
-            StagedSplitBounds splitInfo, int desiredStagePosition) {
+            SplitBounds splitInfo, int desiredStagePosition) {
         boolean isLandscape = dp.isLandscape;
         float topLeftTaskPercent = splitInfo.appsStackedVertically
                 ? splitInfo.topTaskPercent
@@ -551,7 +606,7 @@
 
     @Override
     public void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot,
-            int parentWidth, int parentHeight, StagedSplitBounds splitBoundsConfig,
+            int parentWidth, int parentHeight, SplitBounds splitBoundsConfig,
             DeviceProfile dp, boolean isRtl) {
         int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
         int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
@@ -611,7 +666,7 @@
     public void setSplitIconParams(View primaryIconView, View secondaryIconView,
             int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight,
             int groupedTaskViewHeight, int groupedTaskViewWidth, boolean isRtl,
-            DeviceProfile deviceProfile, StagedSplitBounds splitConfig) {
+            DeviceProfile deviceProfile, SplitBounds splitConfig) {
         FrameLayout.LayoutParams primaryIconParams =
                 (FrameLayout.LayoutParams) primaryIconView.getLayoutParams();
         FrameLayout.LayoutParams secondaryIconParams =
diff --git a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
index 74b6a5b..339f910 100644
--- a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
@@ -19,6 +19,7 @@
 import static android.view.Gravity.BOTTOM;
 import static android.view.Gravity.CENTER_VERTICAL;
 import static android.view.Gravity.END;
+import static android.view.Gravity.RIGHT;
 import static android.view.Gravity.START;
 
 import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
@@ -37,8 +38,9 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.util.SplitConfigurationOptions;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
-import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds;
 import com.android.launcher3.views.BaseDragLayer;
 
 import java.util.Collections;
@@ -105,11 +107,29 @@
         return new PointF(-margin, margin);
     }
 
+    @Override
+    public void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect, SplitBounds splitInfo,
+            int desiredStagePosition) {
+        float topLeftTaskPercent = splitInfo.appsStackedVertically
+                ? splitInfo.topTaskPercent
+                : splitInfo.leftTaskPercent;
+        float dividerBarPercent = splitInfo.appsStackedVertically
+                ? splitInfo.dividerHeightPercent
+                : splitInfo.dividerWidthPercent;
 
+        // In seascape, the primary thumbnail is counterintuitively placed at the physical bottom of
+        // the screen. This is to preserve consistency when the user rotates: From the user's POV,
+        // the primary should always be on the left.
+        if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) {
+            outRect.top += (int) (outRect.height() * (topLeftTaskPercent + dividerBarPercent));
+        } else {
+            outRect.bottom = outRect.top + (int) (outRect.height() * topLeftTaskPercent);
+        }
+    }
 
     @Override
     public Pair<Float, Float> getDwbLayoutTranslations(int taskViewWidth,
-            int taskViewHeight, StagedSplitBounds splitBounds, DeviceProfile deviceProfile,
+            int taskViewHeight, SplitBounds splitBounds, DeviceProfile deviceProfile,
             View[] thumbnailViews, int desiredTaskId, View banner) {
         boolean isRtl = banner.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
         float translationX = 0;
@@ -167,6 +187,29 @@
     }
 
     @Override
+    public void setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight,
+            int splitInstructionsWidth, int threeButtonNavShift) {
+        out.setPivotX(0);
+        out.setPivotY(0);
+        out.setRotation(getDegreesRotated());
+        int distanceToEdge = out.getResources().getDimensionPixelSize(
+                R.dimen.split_instructions_bottom_margin_phone_landscape);
+        // Adjust for any insets on the right edge
+        int insetCorrectionX = dp.getInsets().right;
+        // Center the view in case of unbalanced insets on top or bottom of screen
+        int insetCorrectionY = (dp.getInsets().bottom - dp.getInsets().top) / 2;
+        out.setTranslationX(splitInstructionsWidth - splitInstructionsHeight - distanceToEdge
+                + insetCorrectionX);
+        out.setTranslationY(((splitInstructionsHeight + splitInstructionsWidth) / 2f)
+                + insetCorrectionY);
+        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) out.getLayoutParams();
+        // Setting gravity to RIGHT instead of the lint-recommended END because we always want this
+        // view to be screen-right when phone is in seascape, regardless of the RtL setting.
+        lp.gravity = RIGHT | CENTER_VERTICAL;
+        out.setLayoutParams(lp);
+    }
+
+    @Override
     public void setTaskIconParams(FrameLayout.LayoutParams iconParams,
             int taskIconMargin, int taskIconHeight, int thumbnailTopMargin, boolean isRtl) {
         iconParams.gravity = (isRtl ? END : START) | CENTER_VERTICAL;
@@ -179,7 +222,7 @@
     public void setSplitIconParams(View primaryIconView, View secondaryIconView,
             int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight,
             int groupedTaskViewHeight, int groupedTaskViewWidth, boolean isRtl,
-            DeviceProfile deviceProfile, StagedSplitBounds splitConfig) {
+            DeviceProfile deviceProfile, SplitBounds splitConfig) {
         super.setSplitIconParams(primaryIconView, secondaryIconView, taskIconHeight,
                 primarySnapshotWidth, primarySnapshotHeight, groupedTaskViewHeight,
                 groupedTaskViewWidth, isRtl, deviceProfile, splitConfig);
@@ -191,7 +234,8 @@
         // We calculate the "midpoint" of the thumbnail area, and place the icons there.
         // This is the place where the thumbnail area splits by default, in a near-50/50 split.
         // It is usually not exactly 50/50, due to insets/screen cutouts.
-        int fullscreenInsetThickness = deviceProfile.getInsets().top;
+        int fullscreenInsetThickness = deviceProfile.getInsets().top
+                - deviceProfile.getInsets().bottom;
         int fullscreenMidpointFromBottom = ((deviceProfile.heightPx
                 - fullscreenInsetThickness) / 2);
         float midpointFromBottomPct = (float) fullscreenMidpointFromBottom / deviceProfile.heightPx;
@@ -208,14 +252,14 @@
         if (splitConfig.initiatedFromSeascape) {
             // if the split was initiated from seascape,
             // the task on the right (secondary) is slightly larger
-            primaryIconView.setTranslationY(-bottomToMidpointOffset - insetOffset);
-            secondaryIconView.setTranslationY(-bottomToMidpointOffset - insetOffset
+            primaryIconView.setTranslationY(-bottomToMidpointOffset - insetOffset
                     + taskIconHeight);
+            secondaryIconView.setTranslationY(-bottomToMidpointOffset - insetOffset);
         } else {
             // if not,
             // the task on the left (primary) is slightly larger
-            primaryIconView.setTranslationY(-bottomToMidpointOffset);
-            secondaryIconView.setTranslationY(-bottomToMidpointOffset + taskIconHeight);
+            primaryIconView.setTranslationY(-bottomToMidpointOffset + taskIconHeight);
+            secondaryIconView.setTranslationY(-bottomToMidpointOffset);
         }
 
         primaryIconView.setLayoutParams(primaryIconParams);
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index 7c73be5..15fe1d9 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -41,7 +41,6 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
-import android.util.Pair;
 import android.view.Display;
 
 import androidx.annotation.AnyThread;
@@ -55,6 +54,7 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Objects;
 import java.util.Set;
@@ -66,6 +66,7 @@
 public class DisplayController implements ComponentCallbacks, SafeCloseable {
 
     private static final String TAG = "DisplayController";
+    private static final boolean DEBUG = false;
 
     public static final MainThreadInitializedObject<DisplayController> INSTANCE =
             new MainThreadInitializedObject<>(DisplayController::new);
@@ -116,8 +117,9 @@
                 getPackageFilter(TARGET_OVERLAY_PACKAGE, ACTION_OVERLAY_CHANGED));
 
         WindowManagerProxy wmProxy = WindowManagerProxy.INSTANCE.get(context);
-        mInfo = new Info(getDisplayInfoContext(display), display,
-                wmProxy, wmProxy.estimateInternalDisplayBounds(context));
+        Context displayInfoContext = getDisplayInfoContext(display);
+        mInfo = new Info(displayInfoContext, wmProxy,
+                wmProxy.estimateInternalDisplayBounds(displayInfoContext));
     }
 
     /**
@@ -215,18 +217,18 @@
         WindowManagerProxy wmProxy = WindowManagerProxy.INSTANCE.get(mContext);
         Info oldInfo = mInfo;
 
-        Context displayContext = getDisplayInfoContext(display);
-        Info newInfo = new Info(displayContext, display, wmProxy, oldInfo.mPerDisplayBounds);
+        Context displayInfoContext = getDisplayInfoContext(display);
+        Info newInfo = new Info(displayInfoContext, wmProxy, oldInfo.mPerDisplayBounds);
 
         if (newInfo.densityDpi != oldInfo.densityDpi || newInfo.fontScale != oldInfo.fontScale
                 || newInfo.navigationMode != oldInfo.navigationMode) {
             // Cache may not be valid anymore, recreate without cache
-            newInfo = new Info(displayContext, display, wmProxy,
-                    wmProxy.estimateInternalDisplayBounds(displayContext));
+            newInfo = new Info(displayInfoContext, wmProxy,
+                    wmProxy.estimateInternalDisplayBounds(displayInfoContext));
         }
 
         int change = 0;
-        if (!newInfo.displayId.equals(oldInfo.displayId)) {
+        if (!newInfo.normalizedDisplayInfo.equals(oldInfo.normalizedDisplayInfo)) {
             change |= CHANGE_ACTIVE_SCREEN;
         }
         if (newInfo.rotation != oldInfo.rotation) {
@@ -238,34 +240,18 @@
         if (newInfo.navigationMode != oldInfo.navigationMode) {
             change |= CHANGE_NAVIGATION_MODE;
         }
-        if (!newInfo.supportedBounds.equals(oldInfo.supportedBounds)) {
+        if (!newInfo.supportedBounds.equals(oldInfo.supportedBounds)
+                || !newInfo.mPerDisplayBounds.equals(oldInfo.mPerDisplayBounds)) {
             change |= CHANGE_SUPPORTED_BOUNDS;
-
-            Point currentS = newInfo.currentSize;
-            Pair<CachedDisplayInfo, WindowBounds[]> cachedBounds =
-                    oldInfo.mPerDisplayBounds.get(newInfo.displayId);
-            Point expectedS = cachedBounds == null ? null : cachedBounds.first.size;
-            if (newInfo.supportedBounds.size() != oldInfo.supportedBounds.size()) {
-                Log.e("b/198965093",
-                        "Inconsistent number of displays"
-                                + "\ndisplay state: " + display.getState()
-                                + "\noldInfo.supportedBounds: " + oldInfo.supportedBounds
-                                + "\nnewInfo.supportedBounds: " + newInfo.supportedBounds);
-            }
-            if (expectedS != null
-                    && (Math.min(currentS.x, currentS.y) != Math.min(expectedS.x, expectedS.y)
-                    || Math.max(currentS.x, currentS.y) != Math.max(expectedS.x, expectedS.y))
-                    && display.getState() == Display.STATE_OFF) {
-                Log.e("b/198965093",
-                        "Display size changed while display is off, ignoring change");
-                return;
-            }
+        }
+        if (DEBUG) {
+            Log.d(TAG, "handleInfoChange - change: 0b" + Integer.toBinaryString(change));
         }
 
         if (change != 0) {
             mInfo = newInfo;
             final int flags = change;
-            MAIN_EXECUTOR.execute(() -> notifyChange(displayContext, flags));
+            MAIN_EXECUTOR.execute(() -> notifyChange(displayInfoContext, flags));
         }
     }
 
@@ -283,8 +269,8 @@
     public static class Info {
 
         // Cached property
+        public final CachedDisplayInfo normalizedDisplayInfo;
         public final int rotation;
-        public final String displayId;
         public final Point currentSize;
         public final Rect cutout;
 
@@ -297,51 +283,66 @@
 
         public final Set<WindowBounds> supportedBounds = new ArraySet<>();
 
-        private final ArrayMap<String, Pair<CachedDisplayInfo, WindowBounds[]>> mPerDisplayBounds =
+        private final ArrayMap<CachedDisplayInfo, WindowBounds[]> mPerDisplayBounds =
                 new ArrayMap<>();
 
-        public Info(Context context, Display display) {
+        public Info(Context displayInfoContext) {
             /* don't need system overrides for external displays */
-            this(context, display, new WindowManagerProxy(), new ArrayMap<>());
+            this(displayInfoContext, new WindowManagerProxy(), new ArrayMap<>());
         }
 
         // Used for testing
-        public Info(Context context, Display display,
+        public Info(Context displayInfoContext,
                 WindowManagerProxy wmProxy,
-                ArrayMap<String, Pair<CachedDisplayInfo, WindowBounds[]>> perDisplayBoundsCache) {
-            CachedDisplayInfo displayInfo = wmProxy.getDisplayInfo(context, display);
+                ArrayMap<CachedDisplayInfo, WindowBounds[]> perDisplayBoundsCache) {
+            CachedDisplayInfo displayInfo = wmProxy.getDisplayInfo(displayInfoContext);
+            normalizedDisplayInfo = displayInfo.normalize();
             rotation = displayInfo.rotation;
             currentSize = displayInfo.size;
-            displayId = displayInfo.id;
             cutout = displayInfo.cutout;
 
-            Configuration config = context.getResources().getConfiguration();
+            Configuration config = displayInfoContext.getResources().getConfiguration();
             fontScale = config.fontScale;
             densityDpi = config.densityDpi;
             mScreenSizeDp = new PortraitSize(config.screenHeightDp, config.screenWidthDp);
-            navigationMode = parseNavigationMode(context);
+            navigationMode = parseNavigationMode(displayInfoContext);
 
             mPerDisplayBounds.putAll(perDisplayBoundsCache);
-            Pair<CachedDisplayInfo, WindowBounds[]> cachedValue = mPerDisplayBounds.get(displayId);
+            WindowBounds[] cachedValue = mPerDisplayBounds.get(normalizedDisplayInfo);
 
-            WindowBounds realBounds = wmProxy.getRealBounds(context, display, displayInfo);
+            WindowBounds realBounds = wmProxy.getRealBounds(displayInfoContext, displayInfo);
             if (cachedValue == null) {
-                supportedBounds.add(realBounds);
-            } else {
+                // Unexpected normalizedDisplayInfo is found, recreate the cache
+                Log.e(TAG, "Unexpected normalizedDisplayInfo found, invalidating cache");
+                mPerDisplayBounds.clear();
+                mPerDisplayBounds.putAll(wmProxy.estimateInternalDisplayBounds(displayInfoContext));
+                cachedValue = mPerDisplayBounds.get(normalizedDisplayInfo);
+                if (cachedValue == null) {
+                    Log.e(TAG, "normalizedDisplayInfo not found in estimation: "
+                            + normalizedDisplayInfo);
+                    supportedBounds.add(realBounds);
+                }
+            }
+
+            if (cachedValue != null) {
                 // Verify that the real bounds are a match
-                WindowBounds expectedBounds = cachedValue.second[displayInfo.rotation];
+                WindowBounds expectedBounds = cachedValue[displayInfo.rotation];
                 if (!realBounds.equals(expectedBounds)) {
                     WindowBounds[] clone = new WindowBounds[4];
-                    System.arraycopy(cachedValue.second, 0, clone, 0, 4);
+                    System.arraycopy(cachedValue, 0, clone, 0, 4);
                     clone[displayInfo.rotation] = realBounds;
-                    cachedValue = Pair.create(displayInfo.normalize(), clone);
-                    mPerDisplayBounds.put(displayId, cachedValue);
+                    mPerDisplayBounds.put(normalizedDisplayInfo, clone);
                 }
             }
             mPerDisplayBounds.values().forEach(
-                    pair -> Collections.addAll(supportedBounds, pair.second));
-            Log.d("b/211775278", "displayId: " + displayId + ", currentSize: " + currentSize);
-            Log.d("b/211775278", "perDisplayBounds: " + mPerDisplayBounds);
+                    windowBounds -> Collections.addAll(supportedBounds, windowBounds));
+            if (DEBUG) {
+                Log.d(TAG, "displayInfo: " + displayInfo);
+                Log.d(TAG, "realBounds: " + realBounds);
+                Log.d(TAG, "normalizedDisplayInfo: " + normalizedDisplayInfo);
+                mPerDisplayBounds.forEach((key, value) -> Log.d(TAG,
+                        "perDisplayBounds - " + key + ": " + Arrays.deepToString(value)));
+            }
         }
 
         /**
@@ -369,13 +370,14 @@
     public void dump(PrintWriter pw) {
         Info info = mInfo;
         pw.println("DisplayController.Info:");
-        pw.println("  id=" + info.displayId);
+        pw.println("  normalizedDisplayInfo=" + info.normalizedDisplayInfo);
         pw.println("  rotation=" + info.rotation);
         pw.println("  fontScale=" + info.fontScale);
         pw.println("  densityDpi=" + info.densityDpi);
         pw.println("  navigationMode=" + info.navigationMode.name());
         pw.println("  currentSize=" + info.currentSize);
-        pw.println("  supportedBounds=" + info.supportedBounds);
+        info.mPerDisplayBounds.forEach((key, value) -> pw.println(
+                "  perDisplayBounds - " + key + ": " + Arrays.deepToString(value)));
     }
 
     /**
diff --git a/src/com/android/launcher3/util/HorizontalInsettableView.java b/src/com/android/launcher3/util/HorizontalInsettableView.java
index 7979bc0..486b73d 100644
--- a/src/com/android/launcher3/util/HorizontalInsettableView.java
+++ b/src/com/android/launcher3/util/HorizontalInsettableView.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.util;
 
+import android.util.FloatProperty;
+
 /**
  * Allows the implementing view to add insets to the left and right.
  */
@@ -32,4 +34,22 @@
      */
     void setHorizontalInsets(float insetPercentage);
 
+    /**
+     * Returns the width percentage to inset the content from the left and from the right. See
+     * {@link #setHorizontalInsets};
+     */
+    float getHorizontalInsets();
+
+    FloatProperty<HorizontalInsettableView> HORIZONTAL_INSETS =
+            new FloatProperty<HorizontalInsettableView>("horizontalInsets") {
+                @Override
+                public Float get(HorizontalInsettableView view) {
+                    return view.getHorizontalInsets();
+                }
+
+                @Override
+                public void setValue(HorizontalInsettableView view, float insetPercentage) {
+                    view.setHorizontalInsets(insetPercentage);
+                }
+            };
 }
diff --git a/src/com/android/launcher3/util/PendingSplitSelectInfo.java b/src/com/android/launcher3/util/PendingSplitSelectInfo.java
new file mode 100644
index 0000000..ed02465
--- /dev/null
+++ b/src/com/android/launcher3/util/PendingSplitSelectInfo.java
@@ -0,0 +1,44 @@
+/*
+ * 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 com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
+
+/**
+ * Utility class to store information regarding a split select request. This includes the taskId of
+ * the originating task, plus the stage position.
+ * This information is intended to be saved across launcher instances, e.g. when Launcher needs to
+ * recover straight into a split select state.
+ */
+public class PendingSplitSelectInfo {
+
+    private final int mStagedTaskId;
+    private final int mStagePosition;
+
+    public PendingSplitSelectInfo(int stagedTaskId, int stagePosition) {
+        this.mStagedTaskId = stagedTaskId;
+        this.mStagePosition = stagePosition;
+    }
+
+    public int getStagedTaskId() {
+        return mStagedTaskId;
+    }
+
+    public @StagePosition int getStagePosition() {
+        return mStagePosition;
+    }
+}
diff --git a/src/com/android/launcher3/util/SplitConfigurationOptions.java b/src/com/android/launcher3/util/SplitConfigurationOptions.java
index 6a336cc..f14d985 100644
--- a/src/com/android/launcher3/util/SplitConfigurationOptions.java
+++ b/src/com/android/launcher3/util/SplitConfigurationOptions.java
@@ -96,7 +96,7 @@
      *
      * If you make changes here, consider making the same changes there
      */
-    public static class StagedSplitBounds {
+    public static class SplitBounds {
         public final Rect leftTopBounds;
         public final Rect rightBottomBounds;
         /** This rect represents the actual gap between the two apps */
@@ -124,7 +124,7 @@
         public final int leftTopTaskId;
         public final int rightBottomTaskId;
 
-        public StagedSplitBounds(Rect leftTopBounds, Rect rightBottomBounds, int leftTopTaskId,
+        public SplitBounds(Rect leftTopBounds, Rect rightBottomBounds, int leftTopTaskId,
                 int rightBottomTaskId) {
             this.leftTopBounds = leftTopBounds;
             this.rightBottomBounds = rightBottomBounds;
@@ -163,7 +163,7 @@
         }
     }
 
-    public static class StagedSplitTaskPosition {
+    public static class SplitStageInfo {
         public int taskId = -1;
         @StagePosition
         public int stagePosition = STAGE_POSITION_UNDEFINED;
diff --git a/src/com/android/launcher3/util/SystemUiController.java b/src/com/android/launcher3/util/SystemUiController.java
index 630df7e..6945983 100644
--- a/src/com/android/launcher3/util/SystemUiController.java
+++ b/src/com/android/launcher3/util/SystemUiController.java
@@ -19,6 +19,10 @@
 import android.view.View;
 import android.view.Window;
 
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
 
 /**
@@ -31,15 +35,26 @@
     public static final int UI_STATE_SCRIM_VIEW = 1;
     public static final int UI_STATE_WIDGET_BOTTOM_SHEET = 2;
     public static final int UI_STATE_FULLSCREEN_TASK = 3;
-    public static final int UI_STATE_ALLAPPS = 4;
 
     public static final int FLAG_LIGHT_NAV = 1 << 0;
     public static final int FLAG_DARK_NAV = 1 << 1;
     public static final int FLAG_LIGHT_STATUS = 1 << 2;
     public static final int FLAG_DARK_STATUS = 1 << 3;
 
+    /**
+     * Security type based on WifiConfiguration.KeyMgmt
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, value = {
+            FLAG_LIGHT_NAV,
+            FLAG_DARK_NAV,
+            FLAG_LIGHT_STATUS,
+            FLAG_DARK_STATUS,
+    })
+    public @interface SystemUiControllerFlags {}
+
     private final Window mWindow;
-    private final int[] mStates = new int[5];
+    private final int[] mStates = new int[4];
 
     public SystemUiController(Window window) {
         mWindow = window;
@@ -50,7 +65,7 @@
                 ? (FLAG_LIGHT_NAV | FLAG_LIGHT_STATUS) : (FLAG_DARK_NAV | FLAG_DARK_STATUS));
     }
 
-    public void updateUiState(int uiState, int flags) {
+    public void updateUiState(int uiState, @SystemUiControllerFlags int flags) {
         if (mStates[uiState] == flags) {
             return;
         }
diff --git a/src/com/android/launcher3/util/WindowBounds.java b/src/com/android/launcher3/util/WindowBounds.java
index a15679a..91480e1 100644
--- a/src/com/android/launcher3/util/WindowBounds.java
+++ b/src/com/android/launcher3/util/WindowBounds.java
@@ -76,6 +76,7 @@
                 + "bounds=" + bounds
                 + ", insets=" + insets
                 + ", availableSize=" + availableSize
+                + ", rotationHint=" + rotationHint
                 + '}';
     }
 
diff --git a/src/com/android/launcher3/util/window/CachedDisplayInfo.java b/src/com/android/launcher3/util/window/CachedDisplayInfo.java
index 06b9829..23f37aa 100644
--- a/src/com/android/launcher3/util/window/CachedDisplayInfo.java
+++ b/src/com/android/launcher3/util/window/CachedDisplayInfo.java
@@ -30,7 +30,6 @@
  */
 public class CachedDisplayInfo {
 
-    public final String id;
     public final Point size;
     public final int rotation;
     public final Rect cutout;
@@ -40,11 +39,10 @@
     }
 
     public CachedDisplayInfo(Point size, int rotation) {
-        this("", size, rotation, new Rect());
+        this(size, rotation, new Rect());
     }
 
-    public CachedDisplayInfo(String id, Point size, int rotation, Rect cutout) {
-        this.id = id;
+    public CachedDisplayInfo(Point size, int rotation, Rect cutout) {
         this.size = size;
         this.rotation = rotation;
         this.cutout = cutout;
@@ -62,16 +60,15 @@
 
         Rect newCutout = new Rect(cutout);
         rotateRect(newCutout, deltaRotation(rotation, Surface.ROTATION_0));
-        return new CachedDisplayInfo(id, newSize, Surface.ROTATION_0, newCutout);
+        return new CachedDisplayInfo(newSize, Surface.ROTATION_0, newCutout);
     }
 
     @Override
     public String toString() {
         return "CachedDisplayInfo{"
-                + "id='" + id + '\''
-                + ", size=" + size
-                + ", rotation=" + rotation
+                + "size=" + size
                 + ", cutout=" + cutout
+                + ", rotation=" + rotation
                 + '}';
     }
 
@@ -80,13 +77,13 @@
         if (this == o) return true;
         if (!(o instanceof CachedDisplayInfo)) return false;
         CachedDisplayInfo that = (CachedDisplayInfo) o;
-        return rotation == that.rotation && Objects.equals(id, that.id)
-                && Objects.equals(size, that.size) && Objects.equals(cutout,
-                that.cutout);
+        return rotation == that.rotation
+                && Objects.equals(size, that.size)
+                && Objects.equals(cutout, that.cutout);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(id, size, rotation, cutout);
+        return Objects.hash(size, rotation, cutout);
     }
 }
diff --git a/src/com/android/launcher3/util/window/WindowManagerProxy.java b/src/com/android/launcher3/util/window/WindowManagerProxy.java
index 9665bf9..d5a065a 100644
--- a/src/com/android/launcher3/util/window/WindowManagerProxy.java
+++ b/src/com/android/launcher3/util/window/WindowManagerProxy.java
@@ -16,7 +16,6 @@
 package com.android.launcher3.util.window;
 
 import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 
 import static com.android.launcher3.ResourceUtils.INVALID_RESOURCE_HANDLE;
 import static com.android.launcher3.ResourceUtils.NAVBAR_HEIGHT;
@@ -41,7 +40,6 @@
 import android.hardware.display.DisplayManager;
 import android.os.Build;
 import android.util.ArrayMap;
-import android.util.Pair;
 import android.view.Display;
 import android.view.DisplayCutout;
 import android.view.Surface;
@@ -88,20 +86,12 @@
      * Returns a map of normalized info of internal displays to estimated window bounds
      * for that display
      */
-    public ArrayMap<String, Pair<CachedDisplayInfo, WindowBounds[]>> estimateInternalDisplayBounds(
-            Context context) {
-        Display[] displays = getDisplays(context);
-        ArrayMap<String, Pair<CachedDisplayInfo, WindowBounds[]>> result = new ArrayMap<>();
-        for (Display display : displays) {
-            if (isInternalDisplay(display)) {
-                Context displayContext = Utilities.ATLEAST_S
-                        ? context.createWindowContext(display, TYPE_APPLICATION, null)
-                        : context.createDisplayContext(display);
-                CachedDisplayInfo info = getDisplayInfo(displayContext, display).normalize();
-                WindowBounds[] bounds = estimateWindowBounds(context, info);
-                result.put(info.id, Pair.create(info, bounds));
-            }
-        }
+    public ArrayMap<CachedDisplayInfo, WindowBounds[]> estimateInternalDisplayBounds(
+            Context displayInfoContext) {
+        CachedDisplayInfo info = getDisplayInfo(displayInfoContext).normalize();
+        WindowBounds[] bounds = estimateWindowBounds(displayInfoContext, info);
+        ArrayMap<CachedDisplayInfo, WindowBounds[]> result = new ArrayMap<>();
+        result.put(info, bounds);
         return result;
     }
 
@@ -109,12 +99,11 @@
      * Returns the real bounds for the provided display after applying any insets normalization
      */
     @TargetApi(Build.VERSION_CODES.R)
-    public WindowBounds getRealBounds(Context windowContext,
-            Display display, CachedDisplayInfo info) {
+    public WindowBounds getRealBounds(Context displayInfoContext, CachedDisplayInfo info) {
         if (!Utilities.ATLEAST_R) {
             Point smallestSize = new Point();
             Point largestSize = new Point();
-            display.getCurrentSizeRange(smallestSize, largestSize);
+            getDisplay(displayInfoContext).getCurrentSizeRange(smallestSize, largestSize);
 
             if (info.size.y > info.size.x) {
                 // Portrait
@@ -122,17 +111,16 @@
                         info.rotation);
             } else {
                 // Landscape
-                new WindowBounds(info.size.x, info.size.y, largestSize.x, smallestSize.y,
+                return new WindowBounds(info.size.x, info.size.y, largestSize.x, smallestSize.y,
                         info.rotation);
             }
         }
 
-        WindowMetrics wm = windowContext.getSystemService(WindowManager.class)
+        WindowMetrics windowMetrics = displayInfoContext.getSystemService(WindowManager.class)
                 .getMaximumWindowMetrics();
-
         Rect insets = new Rect();
-        normalizeWindowInsets(windowContext, wm.getWindowInsets(), insets);
-        return new WindowBounds(wm.getBounds(), insets, info.rotation);
+        normalizeWindowInsets(displayInfoContext, windowMetrics.getWindowInsets(), insets);
+        return new WindowBounds(windowMetrics.getBounds(), insets, info.rotation);
     }
 
     /**
@@ -169,12 +157,9 @@
         insetsBuilder.setInsetsIgnoringVisibility(WindowInsets.Type.navigationBars(), newNavInsets);
 
         Insets statusBarInsets = oldInsets.getInsets(WindowInsets.Type.statusBars());
-
-
         int statusBarHeight = getDimenByName(systemRes,
                 (isPortrait) ? STATUS_BAR_HEIGHT_PORTRAIT : STATUS_BAR_HEIGHT_LANDSCAPE,
                 STATUS_BAR_HEIGHT);
-
         Insets newStatusBarInsets = Insets.of(
                 statusBarInsets.left,
                 Math.max(statusBarInsets.top, statusBarHeight),
@@ -202,21 +187,14 @@
     }
 
     /**
-     * Returns true if the display is an internal displays
-     */
-    protected boolean isInternalDisplay(Display display) {
-        return display.getDisplayId() == Display.DEFAULT_DISPLAY;
-    }
-
-    /**
      * Returns a list of possible WindowBounds for the display keyed on the 4 surface rotations
      */
-    public WindowBounds[] estimateWindowBounds(Context context, CachedDisplayInfo display) {
+    protected WindowBounds[] estimateWindowBounds(Context context, CachedDisplayInfo displayInfo) {
         int densityDpi = context.getResources().getConfiguration().densityDpi;
-        int rotation = display.rotation;
-        Rect safeCutout = display.cutout;
+        int rotation = displayInfo.rotation;
+        Rect safeCutout = displayInfo.cutout;
 
-        int minSize = Math.min(display.size.x, display.size.y);
+        int minSize = Math.min(displayInfo.size.x, displayInfo.size.y);
         int swDp = (int) dpiFromPx(minSize, densityDpi);
 
         Resources systemRes;
@@ -255,7 +233,7 @@
         Point tempSize = new Point();
         for (int i = 0; i < 4; i++) {
             int rotationChange = deltaRotation(rotation, i);
-            tempSize.set(display.size.x, display.size.y);
+            tempSize.set(displayInfo.size.x, displayInfo.size.y);
             rotateSize(tempSize, rotationChange);
             Rect bounds = new Rect(0, 0, tempSize.x, tempSize.y);
 
@@ -311,55 +289,58 @@
      * Returns a CachedDisplayInfo initialized for the current display
      */
     @TargetApi(Build.VERSION_CODES.S)
-    public CachedDisplayInfo getDisplayInfo(Context displayContext, Display display) {
-        int rotation = getRotation(displayContext);
-        Rect cutoutRect = new Rect();
-        Point size = new Point();
+    public CachedDisplayInfo getDisplayInfo(Context displayInfoContext) {
+        int rotation = getRotation(displayInfoContext);
         if (Utilities.ATLEAST_S) {
-            WindowMetrics wm = displayContext.getSystemService(WindowManager.class)
+            WindowMetrics windowMetrics = displayInfoContext.getSystemService(WindowManager.class)
                     .getMaximumWindowMetrics();
-            DisplayCutout cutout = wm.getWindowInsets().getDisplayCutout();
-            if (cutout != null) {
-                cutoutRect.set(cutout.getSafeInsetLeft(), cutout.getSafeInsetTop(),
-                        cutout.getSafeInsetRight(), cutout.getSafeInsetBottom());
-            }
-
-            size.set(wm.getBounds().right, wm.getBounds().bottom);
+            return getDisplayInfo(windowMetrics, rotation);
         } else {
+            Point size = new Point();
+            Display display = getDisplay(displayInfoContext);
             display.getRealSize(size);
+            Rect cutoutRect = new Rect();
+            return new CachedDisplayInfo(size, rotation, cutoutRect);
         }
-        return new CachedDisplayInfo(getDisplayId(display), size, rotation, cutoutRect);
     }
 
     /**
-     * Returns a unique ID representing the display
+     * Returns a CachedDisplayInfo initialized for the current display
      */
-    protected String getDisplayId(Display display) {
-        return Integer.toString(display.getDisplayId());
+    @TargetApi(Build.VERSION_CODES.S)
+    protected CachedDisplayInfo getDisplayInfo(WindowMetrics windowMetrics, int rotation) {
+        Point size = new Point(windowMetrics.getBounds().right, windowMetrics.getBounds().bottom);
+        Rect cutoutRect = new Rect();
+        DisplayCutout cutout = windowMetrics.getWindowInsets().getDisplayCutout();
+        if (cutout != null) {
+            cutoutRect.set(cutout.getSafeInsetLeft(), cutout.getSafeInsetTop(),
+                    cutout.getSafeInsetRight(), cutout.getSafeInsetBottom());
+        }
+        return new CachedDisplayInfo(size, rotation, cutoutRect);
     }
 
     /**
-     * Returns rotation of the display associated with the context.
+     * Returns rotation of the display associated with the context, or rotation of DEFAULT_DISPLAY
+     * if the context isn't associated with a display.
      */
-    public int getRotation(Context context) {
-        Display d = null;
+    public int getRotation(Context displayInfoContext) {
+        return getDisplay(displayInfoContext).getRotation();
+    }
+
+    /**
+     *
+     * Returns the display associated with the context, or DEFAULT_DISPLAY if the context isn't
+     * associated with a display.
+     */
+    protected Display getDisplay(Context displayInfoContext) {
         if (Utilities.ATLEAST_R) {
             try {
-                d = context.getDisplay();
+                return displayInfoContext.getDisplay();
             } catch (UnsupportedOperationException e) {
                 // Ignore
             }
         }
-        if (d == null) {
-            d = context.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY);
-        }
-        return d.getRotation();
-    }
-
-    /**
-     * Returns all currently valid logical displays.
-     */
-    protected Display[] getDisplays(Context context) {
-        return context.getSystemService(DisplayManager.class).getDisplays();
+        return displayInfoContext.getSystemService(DisplayManager.class).getDisplay(
+                DEFAULT_DISPLAY);
     }
 }
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHost.java b/src/com/android/launcher3/widget/LauncherAppWidgetHost.java
index fe83f3f..98a960c 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHost.java
@@ -28,6 +28,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.util.SparseArray;
+import android.widget.RemoteViews;
 import android.widget.Toast;
 
 import androidx.annotation.Nullable;
@@ -37,6 +38,7 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.testing.TestLogging;
@@ -70,13 +72,14 @@
     private final ArrayList<ProviderChangedListener> mProviderChangeListeners = new ArrayList<>();
     private final SparseArray<LauncherAppWidgetHostView> mViews = new SparseArray<>();
     private final SparseArray<PendingAppWidgetHostView> mPendingViews = new SparseArray<>();
+    private final SparseArray<LauncherAppWidgetHostView> mDeferredViews = new SparseArray<>();
+    private final SparseArray<RemoteViews> mCachedRemoteViews = new SparseArray<>();
 
     private final Context mContext;
     private int mFlags = FLAG_STATE_IS_NORMAL;
 
     private IntConsumer mAppWidgetRemovedCallback = null;
 
-
     public LauncherAppWidgetHost(Context context) {
         this(context, null);
     }
@@ -95,6 +98,11 @@
         if (mPendingViews.get(appWidgetId) != null) {
             view = mPendingViews.get(appWidgetId);
             mPendingViews.remove(appWidgetId);
+        } else if (mDeferredViews.get(appWidgetId) != null) {
+            // In case the widget view is deferred, we will simply return the deferred view as
+            // opposed to instantiate a new instance of LauncherAppWidgetHostView since launcher
+            // already added the former to the workspace.
+            view = mDeferredViews.get(appWidgetId);
         } else {
             view = new LauncherAppWidgetHostView(context);
         }
@@ -120,12 +128,25 @@
             // widgets upon bind anyway. See issue 14255011 for more context.
         }
 
-        // We go in reverse order and inflate any deferred widget
+        // We go in reverse order and inflate any deferred or cached widget
         for (int i = mViews.size() - 1; i >= 0; i--) {
             LauncherAppWidgetHostView view = mViews.valueAt(i);
             if (view instanceof DeferredAppWidgetHostView) {
                 view.reInflate();
             }
+            if (FeatureFlags.ENABLE_CACHED_WIDGET.get()) {
+                final int appWidgetId = mViews.keyAt(i);
+                if (view == mDeferredViews.get(appWidgetId)) {
+                    // If the widget view was deferred, we'll need to call super.createView here
+                    // to make the binder call to system process to fetch cumulative updates to this
+                    // widget, as well as setting up this view for future updates.
+                    super.createView(view.mLauncher, appWidgetId, view.getAppWidgetInfo());
+                    // At this point #onCreateView should have been called, which in turn returned
+                    // the deferred view. There's no reason to keep the reference anymore, so we
+                    // removed it here.
+                    mDeferredViews.remove(appWidgetId);
+                }
+            }
         }
     }
 
@@ -221,10 +242,28 @@
             CustomWidgetManager.INSTANCE.get(context).onViewCreated(lahv);
             return lahv;
         } else if ((mFlags & FLAG_LISTENING) == 0) {
-            DeferredAppWidgetHostView view = new DeferredAppWidgetHostView(context);
-            view.setAppWidget(appWidgetId, appWidget);
-            mViews.put(appWidgetId, view);
-            return view;
+            // Since the launcher hasn't started listening to widget updates, we can't simply call
+            // super.createView here because the later will make a binder call to retrieve
+            // RemoteViews from system process.
+            // TODO: have launcher always listens to widget updates in background so that this
+            //  check can be removed altogether.
+            if (FeatureFlags.ENABLE_CACHED_WIDGET.get()
+                    && mCachedRemoteViews.get(appWidgetId) != null) {
+                // We've found RemoteViews from cache for this widget, so we will instantiate a
+                // widget host view and populate it with the cached RemoteViews.
+                final LauncherAppWidgetHostView view = new LauncherAppWidgetHostView(context);
+                view.setAppWidget(appWidgetId, appWidget);
+                view.updateAppWidget(mCachedRemoteViews.get(appWidgetId));
+                mDeferredViews.put(appWidgetId, view);
+                mViews.put(appWidgetId, view);
+                return view;
+            } else {
+                // When cache misses, a placeholder for the widget will be returned instead.
+                DeferredAppWidgetHostView view = new DeferredAppWidgetHostView(context);
+                view.setAppWidget(appWidgetId, appWidget);
+                mViews.put(appWidgetId, view);
+                return view;
+            }
         } else {
             try {
                 return super.createView(context, appWidgetId, appWidget);
@@ -281,6 +320,16 @@
     @Override
     public void clearViews() {
         super.clearViews();
+        if (FeatureFlags.ENABLE_CACHED_WIDGET.get()) {
+            // First, we clear any previously cached content from existing widgets
+            mCachedRemoteViews.clear();
+            // Then we proceed to cache the content from the widgets
+            for (int i = 0; i < mViews.size(); i++) {
+                final int appWidgetId = mViews.keyAt(i);
+                final LauncherAppWidgetHostView view = mViews.get(appWidgetId);
+                mCachedRemoteViews.put(appWidgetId, view.mLastRemoteViews);
+            }
+        }
         mViews.clear();
     }
 
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index 0865152..fc1e880 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -43,6 +43,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -85,7 +86,7 @@
     private Runnable mAutoAdvanceRunnable;
 
     private long mDeferUpdatesUntilMillis = 0;
-    private RemoteViews mDeferredRemoteViews;
+    RemoteViews mLastRemoteViews;
     private boolean mHasDeferredColorChange = false;
     private @Nullable SparseIntArray mDeferredColorChange = null;
 
@@ -150,11 +151,18 @@
                     TRACE_METHOD_NAME + getAppWidgetInfo().provider, getAppWidgetId());
             mTrackingWidgetUpdate = false;
         }
-        if (isDeferringUpdates()) {
-            mDeferredRemoteViews = remoteViews;
-            return;
+        if (FeatureFlags.ENABLE_CACHED_WIDGET.get()) {
+            mLastRemoteViews = remoteViews;
+            if (isDeferringUpdates()) {
+                return;
+            }
+        } else {
+            if (isDeferringUpdates()) {
+                mLastRemoteViews = remoteViews;
+                return;
+            }
+            mLastRemoteViews = null;
         }
-        mDeferredRemoteViews = null;
 
         super.updateAppWidget(remoteViews);
 
@@ -218,8 +226,7 @@
         SparseIntArray deferredColors;
         boolean hasDeferredColors;
         mDeferUpdatesUntilMillis = 0;
-        remoteViews = mDeferredRemoteViews;
-        mDeferredRemoteViews = null;
+        remoteViews = mLastRemoteViews;
         deferredColors = mDeferredColorChange;
         hasDeferredColors = mHasDeferredColorChange;
         mDeferredColorChange = null;
diff --git a/src/com/android/launcher3/widget/picker/WidgetPagedView.java b/src/com/android/launcher3/widget/picker/WidgetPagedView.java
new file mode 100644
index 0000000..c95ec5f
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/WidgetPagedView.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.widget.picker;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+
+import com.android.launcher3.PagedView;
+import com.android.launcher3.workprofile.PersonalWorkPagedView;
+
+/**
+ * A {@link PagedView} for showing different widgets for the personal and work profile.
+ */
+public class WidgetPagedView extends PersonalWorkPagedView {
+
+    public WidgetPagedView(Context context) {
+        this(context, null);
+    }
+
+    public WidgetPagedView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public WidgetPagedView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        setPageSpacing(getPaddingLeft());
+    }
+
+    @Override
+    public void getDrawingRect(Rect outRect) {
+        super.getDrawingRect(outRect);
+        outRect.left += getPaddingLeft();
+        outRect.right -= getPaddingRight();
+    }
+}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
index 0e5a7d7..e6b9dca 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
@@ -21,7 +21,6 @@
 import static com.android.launcher3.recyclerview.ViewHolderBinder.POSITION_LAST;
 
 import android.content.Context;
-import android.graphics.Rect;
 import android.os.Process;
 import android.util.Log;
 import android.util.SparseArray;
@@ -36,7 +35,6 @@
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 import androidx.recyclerview.widget.RecyclerView.Adapter;
-import androidx.recyclerview.widget.RecyclerView.LayoutParams;
 import androidx.recyclerview.widget.RecyclerView.ViewHolder;
 
 import com.android.launcher3.R;
@@ -80,10 +78,10 @@
     private static final boolean DEBUG = false;
 
     /** Uniquely identifies widgets list view type within the app. */
-    private static final int VIEW_TYPE_WIDGETS_SPACE = R.id.view_type_widgets_space;
-    private static final int VIEW_TYPE_WIDGETS_LIST = R.id.view_type_widgets_list;
-    private static final int VIEW_TYPE_WIDGETS_HEADER = R.id.view_type_widgets_header;
-    private static final int VIEW_TYPE_WIDGETS_SEARCH_HEADER = R.id.view_type_widgets_search_header;
+    public static final int VIEW_TYPE_WIDGETS_SPACE = R.id.view_type_widgets_space;
+    public static final int VIEW_TYPE_WIDGETS_LIST = R.id.view_type_widgets_list;
+    public static final int VIEW_TYPE_WIDGETS_HEADER = R.id.view_type_widgets_header;
+    public static final int VIEW_TYPE_WIDGETS_SEARCH_HEADER = R.id.view_type_widgets_search_header;
 
     private final Context mContext;
     private final WidgetsDiffReporter mDiffReporter;
@@ -103,7 +101,6 @@
     @Nullable private Predicate<WidgetsListBaseEntry> mFilter = null;
     @Nullable private RecyclerView mRecyclerView;
     @Nullable private PackageUserKey mPendingClickHeader;
-    private final int mSpacingBetweenEntries;
     private int mMaxSpanSize = 4;
 
     public WidgetsListAdapter(Context context, LayoutInflater layoutInflater,
@@ -133,28 +130,11 @@
         mViewHolderBinders.put(
                 VIEW_TYPE_WIDGETS_SPACE,
                 new WidgetsSpaceViewHolderBinder(emptySpaceHeightProvider));
-        mSpacingBetweenEntries =
-                context.getResources().getDimensionPixelSize(R.dimen.widget_list_entry_spacing);
     }
 
     @Override
     public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
         mRecyclerView = recyclerView;
-
-        mRecyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
-            @Override
-            public void getItemOffsets(
-                    @NonNull Rect outRect,
-                    @NonNull View view,
-                    @NonNull RecyclerView parent,
-                    @NonNull RecyclerView.State state) {
-                super.getItemOffsets(outRect, view, parent, state);
-                int position = ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
-                boolean isHeader =
-                        view.getTag(R.id.tag_widget_entry) instanceof WidgetsListBaseEntry.Header;
-                outRect.top += position > 0 && isHeader ? mSpacingBetweenEntries : 0;
-            }
-        });
     }
 
     @Override
@@ -286,7 +266,6 @@
             listPos |= POSITION_LAST;
         }
         viewHolderBinder.bindViewHolder(holder, mVisibleEntries.get(pos), listPos, payloads);
-        holder.itemView.setTag(R.id.tag_widget_entry, entry);
     }
 
     @Override
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListDrawableFactory.java b/src/com/android/launcher3/widget/picker/WidgetsListDrawableFactory.java
index c61e3a4..984a274 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListDrawableFactory.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListDrawableFactory.java
@@ -27,6 +27,7 @@
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.InsetDrawable;
 import android.graphics.drawable.RippleDrawable;
 import android.graphics.drawable.StateListDrawable;
 
@@ -40,6 +41,8 @@
     private final float mMiddleCornerRadius;
     private final ColorStateList mSurfaceColor;
     private final ColorStateList mRippleColor;
+    private final int mVerticalPadding;
+    private final int mHeaderMargin;
 
     WidgetsListDrawableFactory(Context context) {
         Resources res = context.getResources();
@@ -48,6 +51,9 @@
         mSurfaceColor = context.getColorStateList(R.color.surface);
         mRippleColor = ColorStateList.valueOf(
                 Themes.getAttrColor(context, android.R.attr.colorControlHighlight));
+        mVerticalPadding =
+                res.getDimensionPixelSize(R.dimen.widget_list_header_view_vertical_padding);
+        mHeaderMargin = res.getDimensionPixelSize(R.dimen.widget_list_entry_spacing);
     }
 
     /**
@@ -74,7 +80,10 @@
         stateList.addState(
                 LAST.mStateSet,
                 createRoundedRectDrawable(mMiddleCornerRadius, mTopBottomCornerRadius));
-        return new RippleDrawable(mRippleColor, /* content= */ stateList, /* mask= */ stateList);
+        RippleDrawable ripple =
+                new RippleDrawable(mRippleColor, /* content= */ stateList, /* mask= */ stateList);
+        ripple.setPadding(0, mVerticalPadding, 0, mVerticalPadding);
+        return new InsetDrawable(ripple, 0, mHeaderMargin, 0, 0);
     }
 
     /**
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
index bdf646b..35fa7a4 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
@@ -19,24 +19,15 @@
 import android.content.Context;
 import android.graphics.Point;
 import android.util.AttributeSet;
+import android.util.SparseIntArray;
 import android.view.MotionEvent;
-import android.view.View;
-import android.widget.TableLayout;
 
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener;
 
-import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.FastScrollRecyclerView;
 import com.android.launcher3.R;
-import com.android.launcher3.views.ActivityContext;
-import com.android.launcher3.widget.model.WidgetListSpaceEntry;
-import com.android.launcher3.widget.model.WidgetsListBaseEntry;
-import com.android.launcher3.widget.model.WidgetsListContentEntry;
-import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
-import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
-import com.android.launcher3.widget.picker.WidgetsSpaceViewHolderBinder.EmptySpaceView;
 
 /**
  * The widgets recycler view.
@@ -51,12 +42,13 @@
     private boolean mTouchDownOnScroller;
     private HeaderViewDimensionsProvider mHeaderViewDimensionsProvider;
 
-    // Cached sizes
-    private int mLastVisibleWidgetContentTableHeight = 0;
-    private int mWidgetHeaderHeight = 0;
-    private int mWidgetEmptySpaceHeight = 0;
-
-    private final int mSpacingBetweenEntries;
+    /**
+     * There is always 1 or 0 item of VIEW_TYPE_WIDGETS_LIST. Other types have fixes sizes, so the
+     * the size can be used for all other items of same type. Caching the last know size for
+     * VIEW_TYPE_WIDGETS_LIST allows us to use it to estimate full size even when
+     * VIEW_TYPE_WIDGETS_LIST is not visible on the screen.
+     */
+    private final SparseIntArray mCachedSizes = new SparseIntArray();
 
     public WidgetsRecyclerView(Context context) {
         this(context, null);
@@ -71,13 +63,6 @@
         super(context, attrs, defStyleAttr);
         mScrollbarTop = getResources().getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
         addOnItemTouchListener(this);
-
-        ActivityContext activity = ActivityContext.lookupContext(getContext());
-        DeviceProfile grid = activity.getDeviceProfile();
-
-        // The spacing used between entries.
-        mSpacingBetweenEntries =
-                getResources().getDimensionPixelSize(R.dimen.widget_list_entry_spacing);
     }
 
     @Override
@@ -138,67 +123,6 @@
         synchronizeScrollBarThumbOffsetToViewScroll(scrollY, getAvailableScrollHeight());
     }
 
-    @Override
-    public int getCurrentScrollY() {
-        // Skip early if widgets are not bound.
-        if (isModelNotReady() || getChildCount() == 0) {
-            return -1;
-        }
-
-        int rowIndex = -1;
-        View child = null;
-
-        LayoutManager layoutManager = getLayoutManager();
-        if (layoutManager instanceof LinearLayoutManager) {
-            // Use the LayoutManager as the source of truth for visible positions. During
-            // animations, the view group child may not correspond to the visible views that appear
-            // at the top.
-            rowIndex = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();
-            child = layoutManager.findViewByPosition(rowIndex);
-        }
-
-        if (child == null) {
-            // If the layout manager returns null for any reason, which can happen before layout
-            // has occurred for the position, then look at the child of this view as a ViewGroup.
-            child = getChildAt(0);
-            rowIndex = getChildPosition(child);
-        }
-
-        for (int i = 0; i < getChildCount(); i++) {
-            View view = getChildAt(i);
-            if (view instanceof TableLayout) {
-                // This assumes there is ever only one content shown in this recycler view.
-                mLastVisibleWidgetContentTableHeight = view.getMeasuredHeight();
-            } else if (view instanceof WidgetsListHeader
-                    && mWidgetHeaderHeight == 0
-                    && view.getMeasuredHeight() > 0) {
-                // This assumes all header views are of the same height.
-                mWidgetHeaderHeight = view.getMeasuredHeight();
-            } else if (view instanceof EmptySpaceView && view.getMeasuredHeight() > 0) {
-                mWidgetEmptySpaceHeight = view.getMeasuredHeight();
-            }
-        }
-
-        int scrollPosition = getItemsHeight(rowIndex);
-        int offset = getLayoutManager().getDecoratedTop(child);
-
-        return getPaddingTop() + scrollPosition - offset;
-    }
-
-    /**
-     * Returns the available scroll height, in pixel.
-     *
-     * <p>If the recycler view can't be scrolled, returns 0.
-     */
-    @Override
-    protected int getAvailableScrollHeight() {
-        // AvailableScrollHeight = Total height of the all items - first page height
-        int firstPageHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
-        int totalHeightOfAllItems = getItemsHeight(/* untilIndex= */ mAdapter.getItemCount());
-        int availableScrollHeight = totalHeightOfAllItems - firstPageHeight;
-        return Math.max(0, availableScrollHeight);
-    }
-
     private boolean isModelNotReady() {
         return mAdapter.getItemCount() == 0;
     }
@@ -246,28 +170,28 @@
      * <p>If the untilIndex is larger than the total number of items in this adapter, returns the
      * sum of all items' height.
      */
-    private int getItemsHeight(int untilIndex) {
+    @Override
+    protected int getItemsHeight(int untilIndex) {
+        // Initialize cache
+        int childCount = getChildCount();
+        int startPosition;
+        if (childCount > 0
+                && ((startPosition = getChildAdapterPosition(getChildAt(0))) != NO_POSITION)) {
+            int loopCount = Math.min(getChildCount(), getAdapter().getItemCount() - startPosition);
+            for (int i = 0; i < loopCount; i++) {
+                mCachedSizes.put(
+                        mAdapter.getItemViewType(startPosition + i),
+                        getChildAt(i).getMeasuredHeight());
+            }
+        }
+
         if (untilIndex > mAdapter.getItems().size()) {
             untilIndex = mAdapter.getItems().size();
         }
         int totalItemsHeight = 0;
         for (int i = 0; i < untilIndex; i++) {
-            WidgetsListBaseEntry entry = mAdapter.getItems().get(i);
-            if (entry instanceof WidgetsListHeaderEntry
-                    || entry instanceof WidgetsListSearchHeaderEntry) {
-                totalItemsHeight += mWidgetHeaderHeight;
-                if (i > 0) {
-                    // Each header contains the spacing between entries as top decoration, except
-                    // the first one.
-                    totalItemsHeight += mSpacingBetweenEntries;
-                }
-            } else if (entry instanceof WidgetsListContentEntry) {
-                totalItemsHeight += mLastVisibleWidgetContentTableHeight;
-            } else if (entry instanceof WidgetListSpaceEntry) {
-                totalItemsHeight += mWidgetEmptySpaceHeight;
-            } else {
-                throw new UnsupportedOperationException("Can't estimate height for " + entry);
-            }
+            int type = mAdapter.getItemViewType(i);
+            totalItemsHeight += mCachedSizes.get(type);
         }
         return totalItemsHeight;
     }
diff --git a/tests/src/com/android/launcher3/DeviceProfileBaseTest.kt b/tests/src/com/android/launcher3/DeviceProfileBaseTest.kt
index 6d0fcb6..0635d84 100644
--- a/tests/src/com/android/launcher3/DeviceProfileBaseTest.kt
+++ b/tests/src/com/android/launcher3/DeviceProfileBaseTest.kt
@@ -65,6 +65,7 @@
 
         whenever(info.isTablet(any())).thenReturn(false)
         whenever(info.getDensityDpi()).thenReturn(560)
+        whenever(info.smallestSizeDp(any())).thenReturn(411f)
 
         inv = newScalableInvariantDeviceProfile()
     }
@@ -79,6 +80,7 @@
 
         whenever(info.isTablet(any())).thenReturn(true)
         whenever(info.getDensityDpi()).thenReturn(320)
+        whenever(info.smallestSizeDp(any())).thenReturn(800f)
 
         inv = newScalableInvariantDeviceProfile()
     }
@@ -110,6 +112,8 @@
             ).toTypedArray()
             hotseatBorderSpaces = FloatArray(4) { 16f }
             hotseatColumnSpan = IntArray(4) { 4 }
+            hotseatBarBottomSpace = FloatArray(4) { 48f }
+            hotseatQsbSpace = FloatArray(4) { 36f }
             iconSize = FloatArray(4) { 56f }
             allAppsIconSize = FloatArray(4) { 56f }
             iconTextSize = FloatArray(4) { 14f }
diff --git a/tests/src/com/android/launcher3/DeviceProfileGridDimensionsTest.kt b/tests/src/com/android/launcher3/DeviceProfileGridDimensionsTest.kt
deleted file mode 100644
index 80259a5..0000000
--- a/tests/src/com/android/launcher3/DeviceProfileGridDimensionsTest.kt
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3
-
-import android.graphics.PointF
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.launcher3.util.WindowBounds
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers
-import org.mockito.Mockito.`when` as whenever
-
-/**
- * Test for [DeviceProfile] grid dimensions.
- *
- * This includes workspace, cell layout, shortcut and widget container, cell sizes, etc.
- */
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class DeviceProfileGridDimensionsTest : DeviceProfileBaseTest() {
-
-    @Test
-    fun getCellLayoutWidth_twoPanelLandscapeScalable4By4GridTablet_equalsSinglePanelWidth() {
-        val tabletWidth = 2560
-        val tabletHeight = 1600
-        val availableWidth = 2560
-        val availableHeight = 1500
-        windowBounds = WindowBounds(tabletWidth, tabletHeight, availableWidth, availableHeight, 0)
-        useTwoPanels = true
-        whenever(info.isTablet(ArgumentMatchers.any())).thenReturn(true)
-        whenever(info.densityDpi).thenReturn(320)
-        inv = newScalableInvariantDeviceProfile()
-
-        val dp = newDP()
-
-        val expectedWorkspaceWidth = availableWidth
-        val expectedCellLayoutWidth =
-                (expectedWorkspaceWidth - (dp.workspacePadding.right + dp.workspacePadding.left)) /
-                        dp.panelCount
-        assertThat(dp.cellLayoutWidth).isEqualTo(expectedCellLayoutWidth)
-    }
-
-    @Test
-    fun getCellLayoutHeight_twoPanelLandscapeScalable4By4GridTablet_equalsSinglePanelHeight() {
-        val tabletWidth = 2560
-        val tabletHeight = 1600
-        val availableWidth = 2560
-        val availableHeight = 1500
-        windowBounds = WindowBounds(tabletWidth, tabletHeight, availableWidth, availableHeight, 0)
-        useTwoPanels = true
-        whenever(info.isTablet(ArgumentMatchers.any())).thenReturn(true)
-        whenever(info.densityDpi).thenReturn(320)
-        inv = newScalableInvariantDeviceProfile()
-
-        val dp = newDP()
-
-        val expectedWorkspaceHeight = availableHeight
-        val expectedCellLayoutHeight =
-                expectedWorkspaceHeight - (dp.workspacePadding.top + dp.workspacePadding.bottom)
-        assertThat(dp.cellLayoutHeight).isEqualTo(expectedCellLayoutHeight)
-    }
-
-    @Test
-    fun getCellSize_twoPanelLandscapeScalable4By4GridTablet_equalsSinglePanelWidth() {
-        val tabletWidth = 2560
-        val tabletHeight = 1600
-        val availableWidth = 2560
-        val availableHeight = 1500
-        windowBounds = WindowBounds(tabletWidth, tabletHeight, availableWidth, availableHeight, 0)
-        useTwoPanels = true
-        whenever(info.isTablet(ArgumentMatchers.any())).thenReturn(true)
-        whenever(info.densityDpi).thenReturn(320)
-        inv = newScalableInvariantDeviceProfile()
-
-        val dp = newDP()
-
-        val expectedWorkspaceWidth = availableWidth
-        val expectedCellLayoutWidth =
-                (expectedWorkspaceWidth - (dp.workspacePadding.right + dp.workspacePadding.left)) /
-                        dp.panelCount
-        val expectedShortcutAndWidgetContainerWidth =
-                expectedCellLayoutWidth -
-                        (dp.cellLayoutPaddingPx.left + dp.cellLayoutPaddingPx.right)
-        assertThat(dp.getCellSize().x).isEqualTo(
-                (expectedShortcutAndWidgetContainerWidth -
-                        ((inv!!.numColumns - 1) * dp.cellLayoutBorderSpacePx.x)) / inv!!.numColumns)
-        val expectedWorkspaceHeight = availableHeight
-        val expectedCellLayoutHeight =
-                expectedWorkspaceHeight - (dp.workspacePadding.top + dp.workspacePadding.bottom)
-        val expectedShortcutAndWidgetContainerHeight = expectedCellLayoutHeight -
-                (dp.cellLayoutPaddingPx.top + dp.cellLayoutPaddingPx.bottom)
-        assertThat(dp.getCellSize().y).isEqualTo(
-                (expectedShortcutAndWidgetContainerHeight -
-                        ((inv!!.numRows - 1) * dp.cellLayoutBorderSpacePx.y)) / inv!!.numRows)
-    }
-
-    @Test
-    fun getPanelCount_twoPanelLandscapeScalable4By4GridTablet_equalsTwoPanels() {
-        val tabletWidth = 2560
-        val tabletHeight = 1600
-        val availableWidth = 2560
-        val availableHeight = 1500
-        windowBounds = WindowBounds(tabletWidth, tabletHeight, availableWidth, availableHeight, 0)
-        useTwoPanels = true
-        whenever(info.isTablet(ArgumentMatchers.any())).thenReturn(true)
-        whenever(info.densityDpi).thenReturn(320)
-        inv = newScalableInvariantDeviceProfile()
-
-        val dp = newDP()
-
-        assertThat(dp.panelCount).isEqualTo(2)
-    }
-}
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/deviceemulator/DisplayEmulator.java b/tests/src/com/android/launcher3/deviceemulator/DisplayEmulator.java
index 31468c5..e2ed65f 100644
--- a/tests/src/com/android/launcher3/deviceemulator/DisplayEmulator.java
+++ b/tests/src/com/android/launcher3/deviceemulator/DisplayEmulator.java
@@ -47,8 +47,7 @@
      * By changing the WindowManagerProxy we can override the window insets information
      **/
     private IWindowManager changeWindowManagerInstance(DeviceEmulationData deviceData) {
-        WindowManagerProxy.INSTANCE.initializeForTesting(
-                new TestWindowManagerProxy(mContext, deviceData));
+        WindowManagerProxy.INSTANCE.initializeForTesting(new TestWindowManagerProxy(deviceData));
         return WindowManagerGlobal.getWindowManagerService();
     }
 
@@ -57,8 +56,7 @@
         WindowManagerProxy original = WindowManagerProxy.INSTANCE.get(mContext);
         // Set up emulation
         final int userId = UserHandle.myUserId();
-        WindowManagerProxy.INSTANCE.initializeForTesting(
-                new TestWindowManagerProxy(mContext, device));
+        WindowManagerProxy.INSTANCE.initializeForTesting(new TestWindowManagerProxy(device));
         IWindowManager wm = changeWindowManagerInstance(device);
         // Change density twice to force display controller to reset its state
         wm.setForcedDisplayDensityForUser(Display.DEFAULT_DISPLAY, device.density / 2, userId);
diff --git a/tests/src/com/android/launcher3/deviceemulator/TestWindowManagerProxy.java b/tests/src/com/android/launcher3/deviceemulator/TestWindowManagerProxy.java
index cbea688..2d6bbcc 100644
--- a/tests/src/com/android/launcher3/deviceemulator/TestWindowManagerProxy.java
+++ b/tests/src/com/android/launcher3/deviceemulator/TestWindowManagerProxy.java
@@ -19,7 +19,6 @@
 import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.view.Display;
 import android.view.WindowInsets;
 
 import com.android.launcher3.deviceemulator.models.DeviceEmulationData;
@@ -32,17 +31,12 @@
 
     private final DeviceEmulationData mDevice;
 
-    public TestWindowManagerProxy(Context context, DeviceEmulationData device) {
+    public TestWindowManagerProxy(DeviceEmulationData device) {
         super(true);
         mDevice = device;
     }
 
     @Override
-    public boolean isInternalDisplay(Display display) {
-        return display.getDisplayId() == Display.DEFAULT_DISPLAY;
-    }
-
-    @Override
     protected int getDimenByName(Resources res, String resName) {
         Integer mock = mDevice.resourceOverrides.get(resName);
         return mock != null ? mock : super.getDimenByName(res, resName);
@@ -54,27 +48,25 @@
     }
 
     @Override
-    public CachedDisplayInfo getDisplayInfo(Context context, Display display) {
-        int rotation = display.getRotation();
+    public CachedDisplayInfo getDisplayInfo(Context displayInfoContext) {
+        int rotation = getRotation(displayInfoContext);
         Point size = new Point(mDevice.width, mDevice.height);
         RotationUtils.rotateSize(size, rotation);
         Rect cutout = new Rect(mDevice.cutout);
         RotationUtils.rotateRect(cutout, rotation);
-        return new CachedDisplayInfo(getDisplayId(display), size, rotation, cutout);
+        return new CachedDisplayInfo(size, rotation, cutout);
     }
 
     @Override
-    public WindowBounds getRealBounds(Context windowContext, Display display,
-            CachedDisplayInfo info) {
-        return estimateInternalDisplayBounds(windowContext)
-                .get(getDisplayId(display)).second[display.getRotation()];
+    public WindowBounds getRealBounds(Context displayInfoContext, CachedDisplayInfo info) {
+        return estimateInternalDisplayBounds(displayInfoContext).get(
+                getDisplayInfo(displayInfoContext))[getDisplay(displayInfoContext).getRotation()];
     }
 
     @Override
     public WindowInsets normalizeWindowInsets(Context context, WindowInsets oldInsets,
             Rect outInsets) {
-        outInsets.set(getRealBounds(context, context.getDisplay(),
-                getDisplayInfo(context, context.getDisplay())).insets);
+        outInsets.set(getRealBounds(context, getDisplayInfo(context)).insets);
         return oldInsets;
     }
 }
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 8a97c6b..03bf4af 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -25,6 +25,7 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import android.content.Intent;
 import android.graphics.Point;
@@ -53,7 +54,7 @@
 import com.android.launcher3.widget.picker.WidgetsRecyclerView;
 
 import org.junit.Before;
-import org.junit.Rule;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -192,6 +193,17 @@
     }
 
     @Test
+    @PortraitLandscape
+    public void testAllAppsDeadzoneForTablet() throws Exception {
+        assumeTrue(mLauncher.isTablet());
+
+        mLauncher.getWorkspace().switchToAllApps().dismissByTappingOutsideForTablet(
+                true /* tapRight */);
+        mLauncher.getWorkspace().switchToAllApps().dismissByTappingOutsideForTablet(
+                false /* tapRight */);
+    }
+
+    @Test
     @ScreenRecord // b/202433017
     public void testWorkspace() throws Exception {
         final Workspace workspace = mLauncher.getWorkspace();
@@ -199,8 +211,8 @@
         // Test that ensureWorkspaceIsScrollable adds a page by dragging an icon there.
         executeOnLauncher(launcher -> assertFalse("Initial workspace state is scrollable",
                 isWorkspaceScrollable(launcher)));
-        assertNull("Chrome app was found on empty workspace",
-                workspace.tryGetWorkspaceAppIcon("Chrome"));
+        workspace.verifyWorkspaceAppIconIsGone(
+                "Chrome app was found on empty workspace", "Chrome");
 
         workspace.ensureWorkspaceIsScrollable();
 
@@ -376,6 +388,8 @@
 
     @Test
     @PortraitLandscape
+    @ScreenRecord
+    @Ignore // b/233075289
     public void testDragToFolder() {
         // TODO: add the use case to drag an icon to an existing folder. Currently it either fails
         // on tablets or phones due to difference in resolution.
@@ -388,10 +402,10 @@
         folder.getAppIcon(GMAIL_APP_NAME);
         Workspace workspace = folder.close();
 
-        assertNull(STORE_APP_NAME + " should be moved to a folder.",
-                workspace.tryGetWorkspaceAppIcon(STORE_APP_NAME));
-        assertNull(GMAIL_APP_NAME + " should be moved to a folder.",
-                workspace.tryGetWorkspaceAppIcon(GMAIL_APP_NAME));
+        workspace.verifyWorkspaceAppIconIsGone(STORE_APP_NAME + " should be moved to a folder.",
+                STORE_APP_NAME);
+        workspace.verifyWorkspaceAppIconIsGone(GMAIL_APP_NAME + " should be moved to a folder.",
+                GMAIL_APP_NAME);
 
         final HomeAppIcon mapIcon = createShortcutInCenterIfNotExist(MAPS_APP_NAME);
         folderIcon = mapIcon.dragToIcon(folderIcon);
@@ -399,8 +413,8 @@
         folder.getAppIcon(MAPS_APP_NAME);
         workspace = folder.close();
 
-        assertNull(MAPS_APP_NAME + " should be moved to a folder.",
-                workspace.tryGetWorkspaceAppIcon(MAPS_APP_NAME));
+        workspace.verifyWorkspaceAppIconIsGone(MAPS_APP_NAME + " should be moved to a folder.",
+                MAPS_APP_NAME);
     }
 
     @Test
@@ -424,8 +438,9 @@
         for (String appName : new String[]{"Gmail", "Play Store", APP_NAME}) {
             final HomeAppIcon homeAppIcon = createShortcutInCenterIfNotExist(appName);
             Workspace workspace = mLauncher.getWorkspace().deleteAppIcon(homeAppIcon);
-            assertNull(appName + " app was found after being deleted from workspace",
-                    workspace.tryGetWorkspaceAppIcon(appName));
+            workspace.verifyWorkspaceAppIconIsGone(
+                    appName + " app was found after being deleted from workspace",
+                    appName);
         }
     }
 
@@ -509,10 +524,8 @@
                     .containsAtLeast(DUMMY_APP_NAME, MAPS_APP_NAME, STORE_APP_NAME);
 
             mLauncher.getWorkspace().getWorkspaceAppIcon(DUMMY_APP_NAME).uninstall();
-
-            assertNull(
-                    DUMMY_APP_NAME + " app was found after being uninstalled",
-                    mLauncher.getWorkspace().tryGetWorkspaceAppIcon(DUMMY_APP_NAME));
+            mLauncher.getWorkspace().verifyWorkspaceAppIconIsGone(
+                    DUMMY_APP_NAME + " was expected to disappear after uninstall.", DUMMY_APP_NAME);
 
             Map<String, Point> finalPositions =
                     mLauncher.getWorkspace().getWorkspaceIconsPositions();
@@ -522,6 +535,37 @@
         }
     }
 
+    @Test
+    @PortraitLandscape
+    public void testDragShortcutToWorkspaceCell() throws Exception {
+        Point[] targets = getCornersAndCenterPositions();
+
+        for (Point target : targets) {
+            final HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
+            allApps.freeze();
+            try {
+                allApps.getAppIcon(APP_NAME)
+                        .openDeepShortcutMenu()
+                        .getMenuItem(0)
+                        .dragToWorkspace(target.x, target.y);
+            } finally {
+                allApps.unfreeze();
+            }
+        }
+    }
+
+    @Test
+    @PortraitLandscape
+    public void testAddDeleteShortcutOnHotseat() {
+        mLauncher.getWorkspace()
+                .deleteAppIcon(mLauncher.getWorkspace().getHotseatAppIcon(0))
+                .switchToAllApps()
+                .getAppIcon(APP_NAME)
+                .dragToHotseat(0);
+        mLauncher.getWorkspace().deleteAppIcon(
+                mLauncher.getWorkspace().getHotseatAppIcon(APP_NAME));
+    }
+
     /**
      * @return List of workspace grid coordinates. Those are not pixels. See {@link
      *     Workspace#getIconGridDimensions()}
diff --git a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
index 4c41d7e..0a0dfcb 100644
--- a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
+++ b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
@@ -32,7 +32,6 @@
     public FailureWatcher(UiDevice device, LauncherInstrumentation launcher) {
         mDevice = device;
         mLauncher = launcher;
-        Log.d("b/196820244", "FailureWatcher.ctor", new Exception());
     }
 
     @Override
@@ -48,10 +47,8 @@
             public void evaluate() throws Throwable {
                 boolean success = false;
                 try {
-                    Log.d("b/196820244", "Before evaluate");
                     mDevice.executeShellCommand("cmd statusbar tracing start");
                     FailureWatcher.super.apply(base, description).evaluate();
-                    Log.d("b/196820244", "After evaluate");
                     success = true;
                 } finally {
                     // Save artifact for Launcher Winscope trace.
@@ -96,9 +93,7 @@
     public static void onError(LauncherInstrumentation launcher, Description description,
             Throwable e) {
         final UiDevice device = launcher.getDevice();
-        Log.d("b/196820244", "onError 1");
         if (device == null) return;
-        Log.d("b/196820244", "onError 2");
         final File sceenshot = diagFile(description, "TestScreenshot", "png");
         final File hierarchy = diagFile(description, "Hierarchy", "zip");
 
diff --git a/tests/tapl/com/android/launcher3/tapl/Folder.java b/tests/tapl/com/android/launcher3/tapl/Folder.java
index 26f0a8b..1352cc0 100644
--- a/tests/tapl/com/android/launcher3/tapl/Folder.java
+++ b/tests/tapl/com/android/launcher3/tapl/Folder.java
@@ -16,11 +16,6 @@
 
 package com.android.launcher3.tapl;
 
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.os.SystemClock;
-import android.view.MotionEvent;
-
 import androidx.annotation.NonNull;
 import androidx.test.uiautomator.UiObject2;
 
@@ -50,25 +45,15 @@
         }
     }
 
-    private void touchOutsideFolder() {
-        Rect containerBounds = mLauncher.getVisibleBounds(this.mContainer);
-        final long downTime = SystemClock.uptimeMillis();
-        Point containerLeftTopCorner = new Point(containerBounds.left - 1, containerBounds.top - 1);
-        mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN,
-                containerLeftTopCorner, LauncherInstrumentation.GestureScope.INSIDE);
-        mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_UP,
-                containerLeftTopCorner, LauncherInstrumentation.GestureScope.INSIDE);
-    }
-
     /**
-     * CLose opened folder if possible. It throws assertion error if the folder is already closed.
+     * Close opened folder if possible. It throws assertion error if the folder is already closed.
      */
     public Workspace close() {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                      "Want to close opened folder")) {
             mLauncher.waitForLauncherObject(FOLDER_CONTENT_RES_ID);
-            touchOutsideFolder();
+            mLauncher.touchOutsideContainer(this.mContainer, false /* tapRight */);
             mLauncher.waitUntilLauncherObjectGone(FOLDER_CONTENT_RES_ID);
             return mLauncher.getWorkspace();
         }
diff --git a/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java b/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java
index c275f3b..7123de4 100644
--- a/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java
@@ -19,6 +19,7 @@
 import androidx.test.uiautomator.UiObject2;
 
 public class HomeAllApps extends AllApps {
+    private static final String BOTTOM_SHEET_RES_ID = "bottom_sheet_background";
 
     HomeAllApps(LauncherInstrumentation launcher) {
         super(launcher);
@@ -45,4 +46,23 @@
     protected boolean hasSearchBox() {
         return true;
     }
+
+    /**
+     * Taps outside bottom sheet to dismiss and return to workspace. Available on tablets only.
+     * @param tapRight Tap on the right of bottom sheet if true, or left otherwise.
+     */
+    public Workspace dismissByTappingOutsideForTablet(boolean tapRight) {
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+             LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                     "want to tap outside AllApps bottom sheet on the "
+                             + (tapRight ? "right" : "left"))) {
+            final UiObject2 allAppsBottomSheet =
+                    mLauncher.waitForLauncherObject(BOTTOM_SHEET_RES_ID);
+            mLauncher.touchOutsideContainer(allAppsBottomSheet, tapRight);
+            try (LauncherInstrumentation.Closable tapped = mLauncher.addContextLayer(
+                    "tapped outside AllApps bottom sheet")) {
+                return mLauncher.getWorkspace();
+            }
+        }
+    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/HomeAppIcon.java b/tests/tapl/com/android/launcher3/tapl/HomeAppIcon.java
index 71d8ba9..693baa0 100644
--- a/tests/tapl/com/android/launcher3/tapl/HomeAppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/HomeAppIcon.java
@@ -103,40 +103,45 @@
     }
 
     /**
-     * Drag an object to the given cell in workspace. The target cell must be empty.
+     * Drag an object to the given cell in hotseat. The target cell should be expected to be empty.
      *
-     * @param cellX zero based column number, starting from the left of the screen.
-     * @param cellY zero based row number, starting from the top of the screen.
+     * @param cellInd zero based index number of the hotseat cells.
+     * @return the workspace app icon.
      */
-    public HomeAppIcon dragToWorkspace(int cellX, int cellY) {
+    @NonNull
+    public WorkspaceAppIcon dragToHotseat(int cellInd) {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
-                     String.format("want to drag the icon to cell(%d, %d)", cellX, cellY))
+                     String.format("want to drag the icon to hotseat cell %d", cellInd))
         ) {
-            final Supplier<Point> dest = () -> Workspace.getCellCenter(mLauncher, cellX, cellY);
-            Workspace.dragIconToWorkspace(
+            final Supplier<Point> dest = () -> Workspace.getHotseatCellCenter(mLauncher, cellInd);
+
+            Workspace.dragIconToHotseat(
                     mLauncher,
-                    /* launchable= */ this,
+                    this,
                     dest,
                     () -> addExpectedEventsForLongClick(),
                     /*expectDropEvents= */ null);
             try (LauncherInstrumentation.Closable ignore = mLauncher.addContextLayer("dragged")) {
                 WorkspaceAppIcon appIcon =
-                        (WorkspaceAppIcon) mLauncher.getWorkspace().getWorkspaceAppIcon(mAppName);
+                        (WorkspaceAppIcon) mLauncher.getWorkspace().getHotseatAppIcon(mAppName);
                 mLauncher.assertTrue(
-                        String.format(
-                                "The %s icon should be in the cell (%d, %d).", mAppName, cellX,
-                                cellY),
-                        appIcon.isInCell(cellX, cellY));
+                        String.format("The %s icon should be in the hotseat cell %d.", mAppName,
+                                cellInd),
+                        appIcon.isInHotseatCell(cellInd));
                 return appIcon;
             }
         }
     }
 
-
     /** This method requires public access, however should not be called in tests. */
     @Override
     public Launchable getLaunchable() {
         return this;
     }
+
+    boolean isInHotseatCell(int cellInd) {
+        final Point center = Workspace.getHotseatCellCenter(mLauncher, cellInd);
+        return mObject.getVisibleBounds().contains(center.x, center.y);
+    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 9d25b1b..a5f8cf2 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -84,7 +84,6 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Optional;
-import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.function.Consumer;
 import java.util.function.Function;
@@ -101,7 +100,6 @@
     private static final String TAG = "Tapl";
     private static final int ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME = 15;
     private static final int GESTURE_STEP_MS = 16;
-    private static final long FORCE_PAUSE_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(2);
 
     static final Pattern EVENT_TOUCH_DOWN = getTouchEventPattern("ACTION_DOWN");
     static final Pattern EVENT_TOUCH_UP = getTouchEventPattern("ACTION_UP");
@@ -171,7 +169,7 @@
     private static final String WIDGETS_RES_ID = "primary_widgets_list_view";
     private static final String CONTEXT_MENU_RES_ID = "popup_container";
     private static final String TASKBAR_RES_ID = "taskbar_view";
-    public static final int WAIT_TIME_MS = 60000;
+    public static final int WAIT_TIME_MS = 30000;
     private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
     private static final String ANDROID_PACKAGE = "android";
 
@@ -362,10 +360,6 @@
         return getRealDisplaySize().x / 2f;
     }
 
-    private void setForcePauseTimeout(long timeout) {
-        getTestInfo(TestProtocol.REQUEST_SET_FORCE_PAUSE_TIMEOUT, Long.toString(timeout));
-    }
-
     public void setEnableRotation(boolean on) {
         getTestInfo(TestProtocol.REQUEST_ENABLE_ROTATION, Boolean.toString(on));
     }
@@ -886,7 +880,6 @@
             final String action;
             if (getNavigationModel() == NavigationModel.ZERO_BUTTON) {
                 checkForAnomaly(false, true);
-                setForcePauseTimeout(FORCE_PAUSE_TIMEOUT_MS);
 
                 final Point displaySize = getRealDisplaySize();
                 // The swipe up to home gesture starts from inside the launcher when the user is
@@ -1831,4 +1824,26 @@
         return ResourceUtils.getBoolByName(
                 "config_supportsRoundedCornersOnWindows", resources, false);
     }
+
+    /**
+     * Taps outside container to dismiss.
+     * @param container container to be dismissed
+     * @param tapRight tap on the right of the container if true, or left otherwise
+     */
+    void touchOutsideContainer(UiObject2 container, boolean tapRight) {
+        try (LauncherInstrumentation.Closable c = addContextLayer(
+                "want to tap outside container on the " + (tapRight ? "right" : "left"))) {
+            Rect containerBounds = getVisibleBounds(container);
+            final long downTime = SystemClock.uptimeMillis();
+            final Point tapTarget = new Point(
+                    tapRight
+                            ? (containerBounds.right + getRealDisplaySize().x) / 2
+                            : containerBounds.left / 2,
+                    containerBounds.top + 1);
+            sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, tapTarget,
+                    LauncherInstrumentation.GestureScope.INSIDE);
+            sendPointer(downTime, downTime, MotionEvent.ACTION_UP, tapTarget,
+                    LauncherInstrumentation.GestureScope.INSIDE);
+        }
+    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java b/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
index 82652c7..ddeeac2 100644
--- a/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
+++ b/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
@@ -26,6 +26,7 @@
 public class SearchResultFromQsb {
     // The input resource id in the search box.
     private static final String INPUT_RES = "input";
+    private static final String BOTTOM_SHEET_RES_ID = "bottom_sheet_background";
     private final LauncherInstrumentation mLauncher;
 
     SearchResultFromQsb(LauncherInstrumentation launcher) {
@@ -47,4 +48,23 @@
         UiObject2 icon = mLauncher.waitForLauncherObject(By.clazz(TextView.class).text(appName));
         return new AllAppsAppIcon(mLauncher, icon);
     }
+
+    /**
+     * Taps outside bottom sheet to dismiss and return to workspace. Available on tablets only.
+     * @param tapRight Tap on the right of bottom sheet if true, or left otherwise.
+     */
+    public Workspace dismissByTappingOutsideForTablet(boolean tapRight) {
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+             LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                     "want to tap outside AllApps bottom sheet on the "
+                             + (tapRight ? "right" : "left"))) {
+            final UiObject2 allAppsBottomSheet =
+                    mLauncher.waitForLauncherObject(BOTTOM_SHEET_RES_ID);
+            mLauncher.touchOutsideContainer(allAppsBottomSheet, tapRight);
+            try (LauncherInstrumentation.Closable tapped = mLauncher.addContextLayer(
+                    "tapped outside AllApps bottom sheet")) {
+                return mLauncher.getWorkspace();
+            }
+        }
+    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index eb7f05b..5e5fdec 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -39,6 +39,7 @@
 import androidx.test.uiautomator.UiObject2;
 import androidx.test.uiautomator.Until;
 
+import com.android.launcher3.testing.HotseatCellCenterRequest;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.testing.WorkspaceCellCenterRequest;
 
@@ -140,6 +141,20 @@
         }
     }
 
+    /**
+     * Waits for an app icon to be gone (e.g. after uninstall). Fails if it remains.
+     *
+     * @param errorMessage error message thrown then the icon doesn't disappear.
+     * @param appName      app that should be gone.
+     */
+    public void verifyWorkspaceAppIconIsGone(String errorMessage, String appName) {
+        final UiObject2 workspace = verifyActiveContainer();
+        assertTrue(errorMessage,
+                workspace.wait(
+                        Until.gone(AppIcon.getAppIconSelector(appName, mLauncher)),
+                        LauncherInstrumentation.WAIT_TIME_MS));
+    }
+
 
     /**
      * Returns an icon for the app; fails if the icon doesn't exist.
@@ -214,10 +229,11 @@
     private void dragIcon(UiObject2 workspace, HomeAppIcon homeAppIcon, int pageDelta) {
         int pageWidth = mLauncher.getDevice().getDisplayWidth() / pagesPerScreen();
         int targetX = (pageWidth / 2) + pageWidth * pageDelta;
+        int targetY = mLauncher.getVisibleBounds(workspace).centerY();
         dragIconToWorkspace(
                 mLauncher,
                 homeAppIcon,
-                new Point(targetX, mLauncher.getVisibleBounds(workspace).centerY()),
+                () -> new Point(targetX, targetY),
                 false,
                 false,
                 () -> mLauncher.expectEvent(
@@ -236,6 +252,24 @@
     }
 
     /**
+     * Returns an icon for the given cell; fails if the icon doesn't exist.
+     *
+     * @param cellInd zero based index number of the hotseat cells.
+     * @return app icon.
+     */
+    @NonNull
+    public HomeAppIcon getHotseatAppIcon(int cellInd) {
+        List<UiObject2> icons = mHotseat.findObjects(AppIcon.getAnyAppIconSelector());
+        final Point center = getHotseatCellCenter(mLauncher, cellInd);
+        return icons.stream()
+                .filter(icon -> icon.getVisibleBounds().contains(center.x, center.y))
+                .findFirst()
+                .map(icon -> new WorkspaceAppIcon(mLauncher, icon))
+                .orElseThrow(() ->
+                        new AssertionError("Unable to get a hotseat icon on " + cellInd));
+    }
+
+    /**
      * @return map of text -> center of the view. In case of icons with the same name, the one with
      *     lower x coordinate is selected.
      */
@@ -345,6 +379,11 @@
                 TestProtocol.TEST_INFO_RESPONSE_FIELD);
     }
 
+    static Point getHotseatCellCenter(LauncherInstrumentation launcher, int cellInd) {
+        return launcher.getTestInfo(HotseatCellCenterRequest.builder()
+                .setCellInd(cellInd).build()).getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+    }
+
     /**
      * Finds folder icons in the current workspace.
      *
@@ -372,7 +411,7 @@
     }
 
     static void dragIconToWorkspace(LauncherInstrumentation launcher, Launchable launchable,
-            Point dest, boolean startsActivity, boolean isWidgetShortcut,
+            Supplier<Point> dest, boolean startsActivity, boolean isWidgetShortcut,
             Runnable expectLongClickEvents) {
         Runnable expectDropEvents = null;
         if (startsActivity || isWidgetShortcut) {
@@ -380,7 +419,7 @@
                     LauncherInstrumentation.EVENT_START);
         }
         dragIconToWorkspace(
-                launcher, launchable, () -> dest, expectLongClickEvents, expectDropEvents);
+                launcher, launchable, dest, expectLongClickEvents, expectDropEvents);
     }
 
     /**
@@ -456,6 +495,25 @@
                 () -> "Page scroll didn't happen", "Scrolling page");
     }
 
+    static void dragIconToHotseat(
+            LauncherInstrumentation launcher,
+            Launchable launchable,
+            Supplier<Point> dest,
+            Runnable expectLongClickEvents,
+            @Nullable Runnable expectDropEvents) {
+        final long downTime = SystemClock.uptimeMillis();
+        Point dragStart = launchable.startDrag(
+                downTime,
+                expectLongClickEvents,
+                /* runToSpringLoadedState= */ true);
+        Point targetDest = dest.get();
+
+        launcher.movePointer(dragStart, targetDest, DEFAULT_DRAG_STEPS, true,
+                downTime, SystemClock.uptimeMillis(), false,
+                LauncherInstrumentation.GestureScope.INSIDE);
+        dropDraggedIcon(launcher, targetDest, downTime, expectDropEvents);
+    }
+
     /**
      * Flings to get to screens on the right. Waits for scrolling and a possible overscroll
      * recoil to complete.
diff --git a/tests/tapl/com/android/launcher3/tapl/WorkspaceDragSource.java b/tests/tapl/com/android/launcher3/tapl/WorkspaceDragSource.java
index d8d4420..021cc98 100644
--- a/tests/tapl/com/android/launcher3/tapl/WorkspaceDragSource.java
+++ b/tests/tapl/com/android/launcher3/tapl/WorkspaceDragSource.java
@@ -17,6 +17,8 @@
 
 import android.graphics.Point;
 
+import java.util.function.Supplier;
+
 /** Launchable that can serve as a source for dragging and dropping to the workspace. */
 interface WorkspaceDragSource {
 
@@ -36,7 +38,7 @@
             Workspace.dragIconToWorkspace(
                     launcher,
                     launchable,
-                    new Point(
+                    () -> new Point(
                             launchableCenter.x >= width
                                     ? launchableCenter.x - width / 2
                                     : launchableCenter.x + width / 2,
@@ -47,6 +49,40 @@
         }
     }
 
+    /**
+     * Drag an object to the given cell in workspace. The target cell must be empty.
+     *
+     * @param cellX zero based column number, starting from the left of the screen.
+     * @param cellY zero based row number, starting from the top of the screen.     *
+     */
+    default HomeAppIcon dragToWorkspace(int cellX, int cellY) {
+        Launchable launchable = getLaunchable();
+        final String iconName = launchable.getObject().getText();
+        LauncherInstrumentation launcher = launchable.mLauncher;
+        try (LauncherInstrumentation.Closable e = launcher.eventsCheck();
+             LauncherInstrumentation.Closable c = launcher.addContextLayer(
+                     String.format("want to drag the icon to cell(%d, %d)", cellX, cellY))) {
+            final Supplier<Point> dest = () -> Workspace.getCellCenter(launcher, cellX, cellY);
+            Workspace.dragIconToWorkspace(
+                    launcher,
+                    launchable,
+                    dest,
+                    launchable::addExpectedEventsForLongClick,
+                    /*expectDropEvents= */ null);
+
+            try (LauncherInstrumentation.Closable ignore = launcher.addContextLayer("dragged")) {
+                WorkspaceAppIcon appIcon =
+                        (WorkspaceAppIcon) launcher.getWorkspace().getWorkspaceAppIcon(iconName);
+                launcher.assertTrue(
+                        String.format(
+                                "The %s icon should be in the cell (%d, %d).", iconName, cellX,
+                                cellY),
+                        appIcon.isInCell(cellX, cellY));
+                return appIcon;
+            }
+        }
+    }
+
     /** This method requires public access, however should not be called in tests. */
     Launchable getLaunchable();
 }