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

am skip reason: subject contains skip directive

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

Change-Id: Ia55ebdbe4a0b7c6cb8e6887f814f7755a1bb521f
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/Android.bp b/Android.bp
index 0a55675..330c32e 100644
--- a/Android.bp
+++ b/Android.bp
@@ -35,9 +35,7 @@
     ],
     srcs: [
         "tests/tapl/**/*.java",
-        "src/com/android/launcher3/ResourceUtils.java",
-        "src/com/android/launcher3/testing/TestProtocol.java",
-        "src/com/android/launcher3/testing/*Request.java",
+        "src/com/android/launcher3/testing/shared/**/*.java",
     ],
     resource_dirs: [ ],
     manifest: "tests/tapl/AndroidManifest.xml",
@@ -313,3 +311,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/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
index d16e12c..a91ff44 100644
--- a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
+++ b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
@@ -33,6 +33,7 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.ShortcutAndWidgetContainer;
+import com.android.launcher3.testing.shared.TestProtocol;
 
 import java.util.ArrayList;
 import java.util.Collection;
diff --git a/go/src/com/android/launcher3/model/WidgetsModel.java b/go/src/com/android/launcher3/model/WidgetsModel.java
index 1aa5d03..9a000d6 100644
--- a/go/src/com/android/launcher3/model/WidgetsModel.java
+++ b/go/src/com/android/launcher3/model/WidgetsModel.java
@@ -41,8 +41,10 @@
  */
 public class WidgetsModel {
 
-    // True is the widget support is disabled.
+    // True if the widget support is disabled.
     public static final boolean GO_DISABLE_WIDGETS = true;
+    // True if the shortcut support is disabled.
+    public static final boolean GO_DISABLE_SHORTCUTS = true;
     public static final boolean GO_DISABLE_NOTIFICATION_DOTS = true;
 
     private static final ArrayList<WidgetsListBaseEntry> EMPTY_WIDGET_LIST = new ArrayList<>();
diff --git a/proguard.flags b/proguard.flags
index a450183..53a68de 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -2,10 +2,6 @@
   *;
 }
 
--keep class com.android.launcher3.graphics.ShadowDrawable {
-  public <init>(...);
-}
-
 # The support library contains references to newer platform versions.
 # Don't warn about those in case this app is linking against an older
 # platform version.  We know about them, and they are safe.
diff --git a/protos/view_capture.proto b/protos/view_capture.proto
new file mode 100644
index 0000000..f363f36
--- /dev/null
+++ b/protos/view_capture.proto
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package com.android.launcher3.view;
+
+option java_outer_classname = "ViewCaptureData";
+
+message ExportedData {
+
+  repeated FrameData frameData = 1;
+  repeated string classname = 2;
+}
+
+message FrameData {
+  optional int64 timestamp = 1;
+  optional ViewNode node = 2;
+}
+
+message ViewNode {
+  optional int32 classname_index = 1;
+  optional int32 hashcode = 2;
+
+  repeated ViewNode children = 3;
+
+  optional string id = 4;
+  optional int32 left = 5;
+  optional int32 top = 6;
+  optional int32 width = 7;
+  optional int32 height = 8;
+  optional int32 scrollX = 9;
+  optional int32 scrollY = 10;
+
+  optional float translationX = 11;
+  optional float translationY = 12;
+  optional float scaleX = 13 [default = 1];
+  optional float scaleY = 14 [default = 1];
+  optional float alpha = 15 [default = 1];
+
+  optional bool willNotDraw = 16;
+  optional bool clipChildren = 17;
+  optional int32 visibility = 18;
+
+  optional float elevation = 19;
+}
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/ext_tests/src/com/android/quickstep/DebugQuickstepTestInformationHandler.java b/quickstep/ext_tests/src/com/android/quickstep/DebugQuickstepTestInformationHandler.java
index e5f0295..0b17a7b 100644
--- a/quickstep/ext_tests/src/com/android/quickstep/DebugQuickstepTestInformationHandler.java
+++ b/quickstep/ext_tests/src/com/android/quickstep/DebugQuickstepTestInformationHandler.java
@@ -15,22 +15,13 @@
  */
 package com.android.quickstep;
 
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-
 import android.content.Context;
-import android.content.res.Resources;
 import android.os.Bundle;
 
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.BaseQuickstepLauncher;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
-import com.android.launcher3.taskbar.LauncherTaskbarUIController;
 import com.android.launcher3.testing.DebugTestInformationHandler;
-import com.android.launcher3.testing.TestProtocol;
-
-import java.util.concurrent.ExecutionException;
+import com.android.launcher3.testing.shared.TestProtocol;
 
 /**
  * Class to handle requests from tests, including debug ones, to Quickstep Launcher builds.
@@ -47,74 +38,14 @@
     @Override
     public Bundle call(String method, String arg, @Nullable Bundle extras) {
         Bundle response = new Bundle();
-        switch (method) {
-            case TestProtocol.REQUEST_ENABLE_MANUAL_TASKBAR_STASHING:
-                runOnUIThread(l -> {
-                    enableManualTaskbarStashing(l, true);
-                });
-                return response;
-
-            case TestProtocol.REQUEST_DISABLE_MANUAL_TASKBAR_STASHING:
-                runOnUIThread(l -> {
-                    enableManualTaskbarStashing(l, false);
-                });
-                return response;
-
-            case TestProtocol.REQUEST_UNSTASH_TASKBAR_IF_STASHED:
-                runOnUIThread(l -> {
-                    enableManualTaskbarStashing(l, true);
-
-                    BaseQuickstepLauncher quickstepLauncher = (BaseQuickstepLauncher) l;
-                    LauncherTaskbarUIController taskbarUIController =
-                            quickstepLauncher.getTaskbarUIController();
-
-                    // Allow null-pointer to catch illegal states.
-                    taskbarUIController.unstashTaskbarIfStashed();
-
-                    enableManualTaskbarStashing(l, false);
-                });
-                return response;
-
-            case TestProtocol.REQUEST_STASHED_TASKBAR_HEIGHT: {
-                final Resources resources = mContext.getResources();
-                response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
-                        resources.getDimensionPixelSize(R.dimen.taskbar_stashed_size));
-                return response;
-            }
-
-            default:
-                response = super.call(method, arg, extras);
-                if (response != null) return response;
-                return mDebugTestInformationHandler.call(method, arg, extras);
+        if (TestProtocol.REQUEST_RECREATE_TASKBAR.equals(method)) {
+            // Allow null-pointer to catch illegal states.
+            runOnTISBinder(tisBinder -> tisBinder.getTaskbarManager().recreateTaskbar());
+            return response;
         }
-    }
-
-    private void enableManualTaskbarStashing(Launcher launcher, boolean enable) {
-        BaseQuickstepLauncher quickstepLauncher = (BaseQuickstepLauncher) launcher;
-        LauncherTaskbarUIController taskbarUIController =
-                quickstepLauncher.getTaskbarUIController();
-
-        // Allow null-pointer to catch illegal states.
-        taskbarUIController.enableManualStashingForTests(enable);
-    }
-
-    /**
-     * Runs the given command on the UI thread.
-     */
-    private static void runOnUIThread(UIThreadCommand command) {
-        try {
-            MAIN_EXECUTOR.submit(() -> {
-                command.execute(Launcher.ACTIVITY_TRACKER.getCreatedActivity());
-                return null;
-            }).get();
-        } catch (ExecutionException | InterruptedException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    private interface UIThreadCommand {
-
-        void execute(Launcher launcher);
+        response = super.call(method, arg, extras);
+        if (response != null) return response;
+        return mDebugTestInformationHandler.call(method, arg, extras);
     }
 }
 
diff --git a/quickstep/res/drawable/ic_floating_task_button.xml b/quickstep/res/drawable/ic_floating_task_button.xml
new file mode 100644
index 0000000..e50f65c
--- /dev/null
+++ b/quickstep/res/drawable/ic_floating_task_button.xml
@@ -0,0 +1,29 @@
+<?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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M17.6258,4.96L19.0358,6.37L7.4058,18.01L5.9958,16.6L17.6258,4.96ZM16.1358,3.62L4.1258,15.63L3.0158,19.83C2.9058,20.45 3.3858,21 3.9958,21C4.0558,21 4.1058,21 4.1658,20.99L8.3658,19.88L20.3758,7.86C20.7758,7.46 20.9958,6.93 20.9958,6.37C20.9958,5.81 20.7758,5.28 20.3758,4.88L19.1058,3.61C18.7158,3.22 18.1858,3 17.6258,3C17.0658,3 16.5358,3.22 16.1358,3.62Z"
+      android:fillColor="#636C6F"/>
+  <path
+      android:pathData="M20.1936,15.3369C20.3748,16.3837 19.9151,17.5414 18.8846,18.7597C19.1546,18.872 19.4576,18.9452 19.7724,18.9867C20.0839,19.0278 20.3683,19.0325 20.5749,19.0266C20.6772,19.0236 20.7578,19.0181 20.8101,19.0138C20.8362,19.0116 20.855,19.0097 20.8657,19.0085L20.8754,19.0074L20.875,19.0075C21.4217,18.9385 21.9214,19.325 21.9918,19.8718C22.0624,20.4195 21.6756,20.9208 21.1279,20.9914L21,19.9996C21.1279,20.9914 21.1265,20.9916 21.1265,20.9916L21.1249,20.9918L21.1211,20.9923L21.1107,20.9935L21.0795,20.997C21.0542,20.9998 21.0199,21.0032 20.9775,21.0067C20.8929,21.0138 20.7753,21.0216 20.6323,21.0257C20.3481,21.0339 19.9533,21.0279 19.5109,20.9695C18.873,20.8854 18.0393,20.6793 17.3106,20.1662C16.9605,20.3559 16.5876,20.4952 16.2299,20.6003C15.5742,20.7927 14.8754,20.8968 14.2534,20.9534C13.6801,21.0055 13.4553,21.0037 13.1015,21.0008C13.0689,21.0005 13.0352,21.0002 13,21H12.8594C12.8214,21.0002 12.785,21.0006 12.7504,21.0009C12.6524,21.0019 12.5683,21.0027 12.5,21H12.0562C12.0277,21.0003 12.0054,21.0006 11.9926,21.001L11.9751,21H9L11,19H11.9795C11.9929,18.9997 12.0064,18.9997 12.0199,19H12.4117C12.4534,18.9996 12.4864,18.9995 12.5,19H12.9675C12.977,18.9999 12.9878,18.9999 13,19C13.0446,19.0003 13.0859,19.0007 13.1249,19.0011C13.4259,19.0038 13.591,19.0054 14.0723,18.9616C14.6201,18.9118 15.1795,18.8242 15.6665,18.6813C15.753,18.6559 15.8346,18.6295 15.9114,18.6022C15.0315,17.2981 14.7125,16.1044 15.015,15.0829C15.4095,13.7511 16.6784,13.2418 17.7026,13.2864C18.7262,13.3309 19.954,13.9529 20.1936,15.3369ZM16.9327,15.6508C16.873,15.8523 16.8651,16.3878 17.4697,17.334C18.2007,16.4284 18.2585,15.8839 18.2229,15.6781C18.1939,15.5108 18.0297,15.3025 17.6157,15.2845C17.2025,15.2665 16.9885,15.4626 16.9327,15.6508Z"
+      android:fillColor="#636C6F"
+      android:fillType="evenOdd"/>
+</vector>
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/layout/taskbar.xml b/quickstep/res/layout/taskbar.xml
index 3b1d217..94388b4 100644
--- a/quickstep/res/layout/taskbar.xml
+++ b/quickstep/res/layout/taskbar.xml
@@ -45,8 +45,8 @@
             android:id="@+id/start_contextual_buttons"
             android:layout_width="wrap_content"
             android:layout_height="match_parent"
-            android:paddingLeft="@dimen/taskbar_nav_buttons_spacing"
-            android:paddingRight="@dimen/taskbar_nav_buttons_spacing"
+            android:paddingStart="@dimen/taskbar_contextual_button_padding"
+            android:paddingEnd="@dimen/taskbar_contextual_button_padding"
             android:paddingTop="@dimen/taskbar_contextual_padding_top"
             android:gravity="center_vertical"
             android:layout_gravity="start"/>
@@ -56,9 +56,6 @@
             android:layout_width="wrap_content"
             android:layout_height="match_parent"
             android:orientation="horizontal"
-            android:paddingLeft="@dimen/taskbar_nav_buttons_spacing"
-            android:paddingRight="@dimen/taskbar_nav_buttons_spacing"
-            android:layout_marginEnd="@dimen/taskbar_contextual_button_margin"
             android:gravity="center_vertical"
             android:layout_gravity="end"/>
 
@@ -66,8 +63,6 @@
             android:id="@+id/end_contextual_buttons"
             android:layout_width="wrap_content"
             android:layout_height="match_parent"
-            android:paddingLeft="@dimen/taskbar_nav_buttons_spacing"
-            android:paddingRight="@dimen/taskbar_nav_buttons_spacing"
             android:paddingTop="@dimen/taskbar_contextual_padding_top"
             android:gravity="center_vertical"
             android:layout_gravity="end"/>
diff --git a/quickstep/res/values-af/strings.xml b/quickstep/res/values-af/strings.xml
index e834157..34805ea 100644
--- a/quickstep/res/values-af/strings.xml
+++ b/quickstep/res/values-af/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Kry programvoorstelle op jou tuisskerm se gunstelingery"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Kry maklik toegang tot jou programme wat die meeste gebruik word, direk van die tuisskerm af. Voorstelle sal verander op grond van jou roetines. Programme in die onderste ry sal opskuif na jou tuisskerm."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Kry maklik toegang tot jou programme wat die meeste gebruik word, direk van die tuisskerm af. Voorstelle sal verander op grond van jou roetines. Programme in die gunstelingery sal na jou tuisskerm toe skuif."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Kry maklik toegang tot jou programme wat die meeste gebruik word, direk van die tuisskerm af. Voorstelle sal verander op grond van jou roetines. Programme in die onderste ry sal na \'n nuwe vouer toe skuif."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Kry programvoorstelle"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Nee, dankie"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Instellings"</string>
diff --git a/quickstep/res/values-am/strings.xml b/quickstep/res/values-am/strings.xml
index 342bbe2..6ea7d8a 100644
--- a/quickstep/res/values-am/strings.xml
+++ b/quickstep/res/values-am/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"በመነሻ ማያ ገጽዎ የተወዳጆች ረድፍ ላይ የመተግበሪያ አስተያየት ጥቆማዎችን ያግኙ"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"በጣም ስራ ላይ የዋሉ መተግበሪያዎችዎን በቀላሉ ከመነሻ ገጹ ሆነው ይድረሱባቸው። የአስተያየት ጥቆማዎች በእርስዎ ዕለት ተዕለት ተግባራት ላይ በመመስረት ይቀየራሉ። በታችኛው ረድፍ ላይ ያሉ መተግበሪያዎች ወደ መነሻ ገጽዎ ይወሰዳሉ።"</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"በጣም ሥራ ላይ የዋሉ መተግበሪያዎችዎን በቀላሉ ከመነሻ ገጹ ሆነው ይድረሱባቸው። የአስተያየት ጥቆማዎች በእርስዎ ዕለት ተዕለት ተግባራት ላይ በመመሥረት ይቀየራሉ። በተወዳጆች ረድፍ ውስጥ ያሉ መተግበሪያዎች ወደ የእርስዎ መነሻ ማያ ገጽ ይንቀሳቀሳሉ።"</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"በጣም ስራ ላይ የዋሉ መተግበሪያዎችዎን በቀላሉ ከመነሻ ገጹ ሆነው ይድረሱባቸው። የአስተያየት ጥቆማዎች በእርስዎ ዕለት ተዕለት ተግባራት ላይ በመመስረት ይቀየራሉ። በታችኛው ረድፍ ላይ ያሉ መተግበሪያዎች ወደ አዲስ አቃፊ ይወሰዳሉ።"</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"የመተግበሪያ አስተያየት ጥቆማዎችን አግኝ"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"አይ፣ አመሰግናለሁ"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"ቅንብሮች"</string>
diff --git a/quickstep/res/values-ar/strings.xml b/quickstep/res/values-ar/strings.xml
index f15fcd4..f9755d2 100644
--- a/quickstep/res/values-ar/strings.xml
+++ b/quickstep/res/values-ar/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"رؤية التطبيقات المقترحة في صف التطبيقات المفضّلة في الشاشة الرئيسية"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"يمكنك الوصول إلى التطبيقات الأكثر استخدامًا بسهولة من الشاشة الرئيسية مباشرةً. سيتم تغيير الاقتراحات استنادًا إلى استخدامك الروتيني. وسيتم نقل التطبيقات من الصف السفلي في الشاشة الرئيسية إلى الصف الأعلى."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"يمكنك الوصول إلى التطبيقات الأكثر استخدامًا بسهولة من الشاشة الرئيسية مباشرةً. سيتم تغيير الاقتراحات استنادًا إلى سلاسل الإجراءات. سيتم نقل التطبيقات من صف التطبيقات المفضّلة إلى الشاشة الرئيسية."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"يمكنك الوصول إلى التطبيقات الأكثر استخدامًا بسهولة من الشاشة الرئيسية مباشرةً. سيتم تغيير الاقتراحات استنادًا إلى سلاسل الإجراءات. سيتم نقل التطبيقات من الصف الأسفل إلى مجلد جديد."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"رؤية تطبيقات مقترحة"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"لا، شكرًا"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"الإعدادات"</string>
diff --git a/quickstep/res/values-as/strings.xml b/quickstep/res/values-as/strings.xml
index b33abed..ec47dd4 100644
--- a/quickstep/res/values-as/strings.xml
+++ b/quickstep/res/values-as/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"আপোনাৰ গৃহ স্ক্ৰীনৰ প্ৰিয় সমলৰ শাৰীটোত এপৰ পৰামর্শসমূহ পাওক"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"আপোনাৰ সকলোতকৈ বেছিকৈ ব্যৱহৃত এপ্‌সমূহ গৃহ স্ক্ৰীনতে সহজে এক্সেছ কৰক। আপোনাৰ ৰুটিনসমূহৰ ভিত্তিত পৰামর্শসমূহ সলনি হ\'ব। একেবাৰে তলৰ শাৰীটোত থকা এপ্‌সমূহ ওপৰৰ আপোনাৰ গৃহ স্ক্ৰীনলৈ যাব।"</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"আপোনাৰ সকলোতকৈ বেছিকৈ ব্যৱহৃত এপ্‌সমূহ গৃহ স্ক্ৰীনতে সহজে এক্সেছ কৰক। আপোনাৰ ৰুটিনসমূহৰ ভিত্তিত পৰামর্শসমূহ সলনি হ’ব। প্ৰিয় সমলৰ শাৰীত থকা এপ্‌সমূহ আপোনাৰ গৃহ স্ক্রীনলৈ যাব।"</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"আপোনাৰ সকলোতকৈ বেছিকৈ ব্যৱহৃত এপ্‌সমূহ গৃহ স্ক্ৰীনতে সহজে এক্সেছ কৰক। আপোনাৰ ৰুটিনসমূহৰ ভিত্তিত পৰামর্শসমূহ সলনি হ\'ব। একেবাৰে তলৰ শাৰীটোত থকা এপ্‌সমূহ এটা নতুন ফ\'ল্ডাৰলৈ যাব।"</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"এপৰ পৰামর্শসমূহ পাওক"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"নালাগে, ধন্যবাদ"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"ছেটিং"</string>
diff --git a/quickstep/res/values-az/strings.xml b/quickstep/res/values-az/strings.xml
index 07e29bb..7d15232 100644
--- a/quickstep/res/values-az/strings.xml
+++ b/quickstep/res/values-az/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Ana ekranın sevimlilər sırasında tətbiq təklifləri alın"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Birbaşa Ana ekrandan ən çox istifadə edilən tətbiqlərə asanlıqla daxil olun. Təkliflər rejimlərinizə uyğun olaraq dəyişəcək. Aşağı sıradakı tətbiqlər Ana ekrana köçürüləcək."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Birbaşa Ana ekrandan ən çox işlədilən tətbiqlərə asanlıqla girin. Təkliflər rejimlərinizə uyğun olaraq dəyişəcək. Sevimlilər sırasındakı tətbiqlər Əsas ekrana köçürüləcək."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Birbaşa Əsas səhifədən ən çox istifadə edilən tətbiqlərə asanlıqla daxil olun. Təkliflər rejimlərinizə uyğun olaraq dəyişəcək. Aşağı sıradakı tətbiqlər yeni qovluğa köçürüləcək."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Tətbiq təklifləri əldə edin"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Yox, çox sağolun"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Ayarlar"</string>
diff --git a/quickstep/res/values-b+sr+Latn/strings.xml b/quickstep/res/values-b+sr+Latn/strings.xml
index 42023d3..6387953 100644
--- a/quickstep/res/values-b+sr+Latn/strings.xml
+++ b/quickstep/res/values-b+sr+Latn/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Dobijajte predloge aplikacija u redu sa omiljenim stavkama na početnom ekranu"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Lako pristupajte aplikacijama koje najčešće koristite direktno sa početnog ekrana. Predlozi se menjaju na osnovu upotrebe. Aplikacije iz donjeg reda se premeštaju nagore na početni ekran."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Lako pristupajte aplikacijama koje najčešće koristite direktno sa početnog ekrana. Predlozi se menjaju na osnovu vaših rutina. Aplikacije iz reda sa omiljenim stavkama se premeštaju na početni ekran."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Lako pristupajte aplikacijama koje najčešće koristite direktno sa početnog ekrana. Predlozi se menjaju na osnovu upotrebe. Aplikacije iz donjeg reda se premeštaju u nov folder."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Prikazuj predloge aplikacija"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Ne, hvala"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Podešavanja"</string>
diff --git a/quickstep/res/values-be/strings.xml b/quickstep/res/values-be/strings.xml
index dc4dc65..5e5f147 100644
--- a/quickstep/res/values-be/strings.xml
+++ b/quickstep/res/values-be/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Атрымлівайце прапановы праграм у пераліку абраных на Галоўным экране"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Атрымлiвайце доступ да праграм, якімі вы карыстаецеся найбольш часта, непасрэдна з Галоўнага экрана. Прапановы будуць змяняцца ў залежнасці ад вашых дзеянняў. Праграмы, якія знаходзяцца ў ніжнім радку, будуць перамешчаны на Галоўны экран."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Атрымлівайце доступ да праграм, якімі вы карыстаецеся найбольш часта, непасрэдна з Галоўнага экрана. Прапановы будуць змяняцца ў залежнасці ад вашых дзеянняў. Пералік абраных праграм будзе перамешчаны на Галоўны экран."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Атрымлiвайце просты доступ да праграм, якімі вы карыстаецеся найбольш часта, непасрэдна з Галоўнага экрана. Прапановы будуць змяняцца ў залежнасці ад вашых дзеянняў. Праграмы, якія знаходзяцца ў ніжнім радку, будуць перамешчаны ў новую папку."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Атрымліваць прапановы праграм"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Не, дзякуй"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Налады"</string>
diff --git a/quickstep/res/values-bg/strings.xml b/quickstep/res/values-bg/strings.xml
index 560a03b..ca61151 100644
--- a/quickstep/res/values-bg/strings.xml
+++ b/quickstep/res/values-bg/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Получаване на предложения за приложения в реда с любими на началния екран"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Осъществявайте лесен достъп до най-използваните от вас приложения директно от началния екран. Предложенията ще се променят въз основа на действията ви. Приложенията на най-долния ред ще се преместят на началния ви екран."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Осъществявайте лесен достъп до най-използваните от вас приложения директно от началния екран. Предложенията ще се променят въз основа на действията ви. Приложенията в реда с любими ще бъдат преместени на началния екран."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Осъществявайте лесен достъп до най-използваните от вас приложения директно от началния екран. Предложенията ще се променят въз основа на действията ви. Приложенията на най-долния ред ще се преместят в нова папка."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Предложения"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Не, благодаря"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Настройки"</string>
diff --git a/quickstep/res/values-bn/strings.xml b/quickstep/res/values-bn/strings.xml
index d4774a7..268e736 100644
--- a/quickstep/res/values-bn/strings.xml
+++ b/quickstep/res/values-bn/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"হোম স্ক্রিনের \'ফেভারিট রো\' বিকল্পের জন্য অ্যাপ সাজেশন পান"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"হোম স্ক্রিন থেকে সরাসরি সব থেকে বেশি ব্যবহার করা অ্যাপগুলি অ্যাক্সেস করুন। আপনার রুটিনের উপর ভিত্তি করে সাজেশন পরির্তন করা হবে। নিচের সারিতে থাকা অ্যাপ আপনার হোম স্ক্রিনে সরানো হবে।"</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"খুব বেশি ব্যবহার করেন এমন অ্যাপগুলি হোম স্ক্রিন থেকে সহজে সরাসরি অ্যাক্সেস করুন। আপনার রুটিন অনুযায়ী সাজেশন পরির্তন করা হবে। \'ফেভারিট রো\' বিকল্পে থাকা অ্যাপগুলি হোম স্ক্রিনে সরিয়ে দেওয়া হবে।"</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"হোম স্ক্রিনের পাশে সব থেকে ব্যবহার করা অ্যাপ সহজেই অ্যাক্সেস করুন। আপনার রুটিনের উপর ভিত্তি করে সাজেশন পরির্তন করা হবে। নিচের সারিতে থাকা অ্যাপ নতুন ফোল্ডারে সরানো হবে।"</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"অ্যাপ সাজেশন পান"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"না থাক"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"সেটিংস"</string>
diff --git a/quickstep/res/values-bs/strings.xml b/quickstep/res/values-bs/strings.xml
index 3743e24..1ff0817 100644
--- a/quickstep/res/values-bs/strings.xml
+++ b/quickstep/res/values-bs/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Primajte prijedloge aplikacija u redu omiljenih stavki početnog ekrana"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Jednostavno pristupite najčešće korištenim aplikacijama direktno s početnog ekrana. Prijedlozi će se mijenjati na osnovu vaših rutina. Aplikacije koje se nalaze u donjem redu će se premjestiti na početni ekran."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Jednostavno pristupite najčešće korištenim aplikacijama direktno s početnog ekrana. Prijedlozi će se mijenjati na osnovu vaših rutina. Aplikacije u redu omiljenih stavki će se premjestiti na početni ekran."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Jednostavno pristupite najčešće korištenim aplikacijama, direktno s početnog ekrana. Prijedlozi će se mijenjati na osnovu vaših rutina. Aplikacije koje se nalaze u donjem redu će se premjestiti u novi folder."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Prikaži prijedloge aplikacija"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Ne, hvala"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Postavke"</string>
diff --git a/quickstep/res/values-ca/strings.xml b/quickstep/res/values-ca/strings.xml
index 9400fba..4957aee 100644
--- a/quickstep/res/values-ca/strings.xml
+++ b/quickstep/res/values-ca/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Obtén suggeriments d\'aplicacions a la fila Preferides de la teva pantalla d\'inici"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Accedeix fàcilment a les aplicacions que més utilitzes des de la pantalla d\'inici. Els suggeriments variaran en funció dels teus hàbits. Les aplicacions de la fila inferior pujaran a la pantalla d\'inici."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Accedeix fàcilment a les aplicacions que més utilitzes des de la pantalla d\'inici. Els suggeriments variaran en funció dels teus hàbits. Les aplicacions de la fila Preferides es mouran a la teva pantalla d\'inici."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Accedeix fàcilment a les aplicacions que més utilitzes des de la pantalla d\'inici. Els suggeriments variaran en funció dels teus hàbits. Les aplicacions de la fila inferior es mouran a una carpeta nova."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Mostra suggeriments d\'aplicacions"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"No, gràcies"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Configuració"</string>
diff --git a/quickstep/res/values-cs/strings.xml b/quickstep/res/values-cs/strings.xml
index cb49a35..fa707ac 100644
--- a/quickstep/res/values-cs/strings.xml
+++ b/quickstep/res/values-cs/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Nechte si na řádku oblíbených na ploše zobrazovat návrhy aplikací"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Mějte nejpoužívanější aplikace k dispozici přímo na ploše. Návrhy se budou měnit podle vašich zvyklostí. Aplikace ve spodním řádku se přesunou nahoru na vaši plochu."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Mějte nejpoužívanější aplikace k dispozici přímo na ploše. Návrhy se budou měnit podle vašich zvyklostí. Aplikace na řádku oblíbených se přesunou na plochu."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Mějte nejpoužívanější aplikace k dispozici přímo na ploše. Návrhy se budou měnit podle vašich zvyklostí. Aplikace ve spodním řádku se přesunou do nové složky."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Zobrazovat návrhy aplikací"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Ne, díky"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Nastavení"</string>
diff --git a/quickstep/res/values-da/strings.xml b/quickstep/res/values-da/strings.xml
index 621006b..d88061d 100644
--- a/quickstep/res/values-da/strings.xml
+++ b/quickstep/res/values-da/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Få appforslag i rækken med favoritter på din startskærm"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Få nem adgang til dine mest brugte apps direkte fra startskærmen. Forslagene ændres ud fra dine vaner. Apps i nederste række bliver flyttet op til din startskærm."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Få nem adgang til dine mest brugte apps direkte fra startskærmen. Forslagene ændres ud fra dine vaner. Apps i rækken med favoritter bliver flyttet til din startskærm."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Få nem adgang til dine mest brugte apps direkte fra startskærmen. Forslagene ændres ud fra dine vaner. Apps i nederste række bliver flyttet til en ny mappe."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Få appforslag"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Nej tak"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Indstillinger"</string>
diff --git a/quickstep/res/values-de/strings.xml b/quickstep/res/values-de/strings.xml
index d19c23b..3728b35 100644
--- a/quickstep/res/values-de/strings.xml
+++ b/quickstep/res/values-de/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Lass dir in der Favoritenleiste auf dem Startbildschirm App-Vorschläge anzeigen"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Schneller Zugriff auf deine meistverwendeten Apps direkt über den Startbildschirm. Die Vorschläge werden deiner Nutzung entsprechend laufend angepasst. Apps in der unteren Reihe werden nach oben auf den Startbildschirm verschoben."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Schneller Zugriff auf deine meistverwendeten Apps direkt über den Startbildschirm. Die Vorschläge werden deiner Nutzung entsprechend laufend angepasst. Apps der Favoritenleiste werden auf den Startbildschirm verschoben."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Schneller Zugriff auf deine meistverwendeten Apps direkt über den Startbildschirm. Die Vorschläge werden deiner Nutzung entsprechend laufend angepasst. Apps in der unteren Reihe werden in einen neuen Ordner verschoben."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"App-Vorschläge erhalten"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Nein danke"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Einstellungen"</string>
diff --git a/quickstep/res/values-el/strings.xml b/quickstep/res/values-el/strings.xml
index 2c6702b..5ff882e 100644
--- a/quickstep/res/values-el/strings.xml
+++ b/quickstep/res/values-el/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Δείτε τις προτεινόμενες εφαρμογές στη σειρά Αγαπημένα της αρχικής οθόνης."</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Αποκτήστε εύκολα πρόσβαση στις εφαρμογές που χρησιμοποιείτε περισσότερο απευθείας από την αρχική οθόνη. Οι προτάσεις θα αλλάζουν με βάση τις ρουτίνες σας. Οι εφαρμογές στην κάτω σειρά θα μετακινηθούν προς τα επάνω στην αρχική οθόνη."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Αποκτήστε εύκολα πρόσβαση στις εφαρμογές που χρησιμοποιείτε περισσότερο απευθείας από την αρχική οθόνη. Οι προτάσεις θα αλλάζουν με βάση τις ρουτίνες σας. Οι εφαρμογές στην σειρά Αγαπημένα θα μετακινηθούν στην αρχική οθόνη σας."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Αποκτήστε εύκολα πρόσβαση στις εφαρμογές που χρησιμοποιείτε περισσότερο, απευθείας από την αρχική οθόνη. Οι προτάσεις θα αλλάζουν με βάση τις ρουτίνες σας. Οι εφαρμογές στην κάτω σειρά θα μεταφερθούν σε νέο φάκελο."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Προβολή προτεινόμενων εφαρμογών"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Όχι, ευχαριστώ"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Ρυθμίσεις"</string>
diff --git a/quickstep/res/values-en-rAU/strings.xml b/quickstep/res/values-en-rAU/strings.xml
index 7b297af..5b378b4 100644
--- a/quickstep/res/values-en-rAU/strings.xml
+++ b/quickstep/res/values-en-rAU/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Get app suggestions on the favourites row of your home screen"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Easily access your most-used apps directly from the home screen. Suggestions will change based on your routines. Apps on the bottom row will move up to your home screen."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Easily access your most-used apps directly from the home screen. Suggestions will change based on your routines. Apps in the favourites row will move to your home screen."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Easily access your most-used apps directly from the home screen. Suggestions will change based on your routines. Apps on the bottom row will be moved to a new folder."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Get app suggestions"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"No, thanks"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Settings"</string>
diff --git a/quickstep/res/values-en-rCA/strings.xml b/quickstep/res/values-en-rCA/strings.xml
index 4013a71..ab0a8e9 100644
--- a/quickstep/res/values-en-rCA/strings.xml
+++ b/quickstep/res/values-en-rCA/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Get app suggestions on the favourites row of your home screen"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Easily access your most-used apps directly from the home screen. Suggestions will change based on your routines. Apps on the bottom row will move up to your home screen."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Easily access your most-used apps directly from the home screen. Suggestions will change based on your routines. Apps in the favourites row will move to your home screen."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Easily access your most-used apps directly from the home screen. Suggestions will change based on your routines. Apps on the bottom row will be moved to a new folder."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Get app suggestions"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"No, thanks"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Settings"</string>
diff --git a/quickstep/res/values-en-rGB/strings.xml b/quickstep/res/values-en-rGB/strings.xml
index 7b297af..5b378b4 100644
--- a/quickstep/res/values-en-rGB/strings.xml
+++ b/quickstep/res/values-en-rGB/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Get app suggestions on the favourites row of your home screen"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Easily access your most-used apps directly from the home screen. Suggestions will change based on your routines. Apps on the bottom row will move up to your home screen."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Easily access your most-used apps directly from the home screen. Suggestions will change based on your routines. Apps in the favourites row will move to your home screen."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Easily access your most-used apps directly from the home screen. Suggestions will change based on your routines. Apps on the bottom row will be moved to a new folder."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Get app suggestions"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"No, thanks"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Settings"</string>
diff --git a/quickstep/res/values-en-rIN/strings.xml b/quickstep/res/values-en-rIN/strings.xml
index 7b297af..5b378b4 100644
--- a/quickstep/res/values-en-rIN/strings.xml
+++ b/quickstep/res/values-en-rIN/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Get app suggestions on the favourites row of your home screen"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Easily access your most-used apps directly from the home screen. Suggestions will change based on your routines. Apps on the bottom row will move up to your home screen."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Easily access your most-used apps directly from the home screen. Suggestions will change based on your routines. Apps in the favourites row will move to your home screen."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Easily access your most-used apps directly from the home screen. Suggestions will change based on your routines. Apps on the bottom row will be moved to a new folder."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Get app suggestions"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"No, thanks"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Settings"</string>
diff --git a/quickstep/res/values-en-rXC/strings.xml b/quickstep/res/values-en-rXC/strings.xml
index bd2a024..f0b2066 100644
--- a/quickstep/res/values-en-rXC/strings.xml
+++ b/quickstep/res/values-en-rXC/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‎‎‏‎‎‏‏‎‏‏‏‎‎‏‎‏‏‎‏‏‏‏‎‏‏‏‎‎‏‏‏‏‏‎‏‏‎‎‎‏‎‎‏‏‎‏‎‎‎‎‏‎‏‏‏‏‏‎‏‏‎Get app suggestions on favorites row of your Home screen‎‏‎‎‏‎"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‏‎‎‎‏‏‏‎‏‏‎‏‏‎‎‎‏‏‏‏‎‏‎‎‏‏‏‏‏‎‎‏‎‏‏‎‎‏‏‎‎‏‏‎‏‏‏‏‎‏‏‎‎‎‎Easily access your most-used apps right on the Home screen. Suggestions will change based on your routines. Apps on the bottom row will move up to your Home screen.‎‏‎‎‏‎"</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‏‎‏‏‏‏‎‏‏‏‎‏‎‎‎‏‏‏‎‏‏‏‏‏‏‎‎‎‎‏‏‏‏‎‎‎‎‏‎‎‏‏‎‏‎‏‎‏‏‏‎‎‏‏‎‎‎‏‎Easily access your most-used apps right on the Home screen. Suggestions will change based on your routines. Apps in favorites row will move to your Home screen.‎‏‎‎‏‎"</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‏‎‏‎‎‎‏‏‏‎‎‎‏‎‏‎‎‎‏‎‎‏‏‎‏‎‎‎‏‎‏‏‏‏‏‎‎‎‏‎‏‏‎‎‎‏‎‏‏‎‏‎‏‎‎‏‎‏‎‎‎Easily access your most-used apps, right on the Home screen. Suggestions will change based on your routines. Apps on the bottom row will move to a new folder.‎‏‎‎‏‎"</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‎‏‏‎‏‏‎‎‏‎‏‏‏‎‏‎‏‎‏‏‎‏‎‏‏‎‎‏‏‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‏‎‎‎‏‏‎‎‎‏‎‎‎‏‏‏‎‏‎Get app suggestions‎‏‎‎‏‎"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‎‎‏‏‎‎‎‏‎‏‎‏‎‏‏‏‏‏‏‎‎‎‏‎‏‎‎‏‏‎‏‏‎‏‏‎‎‎‏‎‎‎‏‏‎‏‎‏‏‎‏‏‎‎‏‎No thanks‎‏‎‎‏‎"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‎‏‏‎‎‎‎‎‎‏‏‏‎‎‎‏‎‏‎‏‎‏‏‏‏‎‎‎‏‎‏‎‏‏‏‎‎‏‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‏‎‎Settings‎‏‎‎‏‎"</string>
diff --git a/quickstep/res/values-es-rUS/strings.xml b/quickstep/res/values-es-rUS/strings.xml
index 06ee5bb..b9bb70f 100644
--- a/quickstep/res/values-es-rUS/strings.xml
+++ b/quickstep/res/values-es-rUS/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Obtén sugerencias de apps en la fila de favoritos de la pantalla principal"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Accede fácilmente en la pantalla principal a las apps que más usas. Las sugerencias cambiarán según tus rutinas. Las apps de la fila inferior se desplazarán hacia arriba en la pantalla principal."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Accede fácilmente en la pantalla principal a las apps que más usas. Las sugerencias cambiarán según tus rutinas. Se moverán a la pantalla principal las apps que estén en la fila de favoritos."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Accede fácilmente a las apps que más usas en la pantalla principal. Las sugerencias cambiarán según tus rutinas. Las apps de la fila inferior se moverán a una nueva carpeta."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Obtén sugerencias de aplicaciones"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"No, gracias"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Configuración"</string>
diff --git a/quickstep/res/values-es/strings.xml b/quickstep/res/values-es/strings.xml
index 80c8b0b..f3d7a31 100644
--- a/quickstep/res/values-es/strings.xml
+++ b/quickstep/res/values-es/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Recibe sugerencias de aplicaciones en la fila de aplicaciones favoritas de la pantalla de inicio"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Accede fácilmente a las aplicaciones que más usas desde la pantalla de inicio. Las sugerencias cambiarán según tus hábitos. Las aplicaciones de la fila inferior que tengas ahora se moverán hacia arriba en la pantalla de inicio."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Accede fácilmente a las aplicaciones que más usas desde la pantalla de inicio. Las sugerencias cambiarán según tus hábitos. Las aplicaciones de la fila de aplicaciones favoritas se moverán a la pantalla de inicio."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Accede fácilmente a las aplicaciones que más usas desde la pantalla de inicio. Las sugerencias cambiarán según tus hábitos. Las aplicaciones de la fila inferior que tengas ahora se moverán a una carpeta nueva."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Sí, obtener sugerencias"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"No, gracias"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Ajustes"</string>
diff --git a/quickstep/res/values-et/strings.xml b/quickstep/res/values-et/strings.xml
index 7f37a4b..73c1c1d 100644
--- a/quickstep/res/values-et/strings.xml
+++ b/quickstep/res/values-et/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Hankige avakuva lemmikute reale rakenduste soovitusi"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Pääsete enim kasutatavatele rakendustele hõlpsasti juurde otse avakuvalt. Soovitused muutuvad olenevalt teie rutiinist. Alumisel real olevad rakendused teisaldatakse teie avakuvale."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Pääsete enim kasutatavatele rakendustele hõlpsasti juurde otse avakuvalt. Soovitused muutuvad olenevalt teie rutiinist. Lemmikute real olevad rakendused teisaldatakse teie avakuvale."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Pääsete enim kasutatavatele rakendustele hõlpsasti juurde otse avakuvalt. Soovitused muutuvad olenevalt teie rutiinist. Alumisel real olevad rakendused teisaldatakse uude kausta."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Hangi rakenduste soovitusi"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Tänan, ei"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Seaded"</string>
diff --git a/quickstep/res/values-eu/strings.xml b/quickstep/res/values-eu/strings.xml
index 4169838..5b83633 100644
--- a/quickstep/res/values-eu/strings.xml
+++ b/quickstep/res/values-eu/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Jaso aplikazioen iradokizunak hasierako pantailako gogokoen errenkadan"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Atzitu erraz aplikazio erabilienak hasierako pantailatik bertatik. Ohituren arabera aldatuko dira iradokizunak. Hasierako pantailara eramango dira beheko errenkadan dauden aplikazioak."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Atzitu erraz aplikazio erabilienak hasierako pantailatik bertatik. Ohituren arabera aldatuko dira iradokizunak. Gogokoen errenkadako aplikazioak hasierako pantailara eramango ditugu."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Atzitu erraz aplikazio erabilienak hasierako pantailatik bertatik. Ohituren arabera aldatuko dira iradokizunak. Karpeta berri batera eramango dira beheko errenkadan dauden aplikazioak."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Jaso aplikazioen iradokizunak"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Ez, eskerrik asko"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Ezarpenak"</string>
diff --git a/quickstep/res/values-fa/strings.xml b/quickstep/res/values-fa/strings.xml
index de6d7e5..a64a876 100644
--- a/quickstep/res/values-fa/strings.xml
+++ b/quickstep/res/values-fa/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"دریافت «پیشنهاد برنامه» در ردیف موارد دلخواه صفحه اصلی"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"به‌راحتی در صفحه اصلی به پرکاربردترین برنامه‌ها دسترسی داشته باشید. پیشنهادها براساس روال‌هایتان تغییر خواهد کرد. برنامه‌های ردیف پایین در صفحه اصلی به بالا منتقل خواهند شد."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"به‌راحتی در صفحه اصلی به پرکاربردترین برنامه‌ها دسترسی داشته باشید. پیشنهادها براساس روال‌هایتان تغییر خواهد کرد. برنامه‌های موجود در ردیف موارد دلخواه به صفحه اصلی منتقل می‌شوند."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"به‌راحتی در صفحه اصلی به پرکاربردترین برنامه‌ها دسترسی داشته باشید. پیشنهادها براساس روال‌هایتان تغییر خواهد کرد. برنامه‌های ردیف پایین به پوشه جدیدی منتقل خواهند شد."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"دریافت پیشنهادهای برنامه"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"نه متشکرم"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"تنظیمات"</string>
diff --git a/quickstep/res/values-fi/strings.xml b/quickstep/res/values-fi/strings.xml
index 8c50f70..08a9248 100644
--- a/quickstep/res/values-fi/strings.xml
+++ b/quickstep/res/values-fi/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Näytä sovellusehdotuksia aloitusnäytön Suosikit-rivillä"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Voit avata käytetyimmät sovellukset kätevästi aloitusnäytöltä. Ehdotukset muuttuvat rutiiniesi perusteella. Alimmalla rivillä olevat sovellukset siirretään aloitusnäytön yläosaan."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Voit avata käytetyimmät sovellukset kätevästi aloitusnäytöltä. Ehdotukset muuttuvat rutiiniesi perusteella. Suosikit-rivillä olevat sovellukset siirretään aloitusnäytölle."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Voit avata käytetyimmät sovellukset kätevästi aloitusnäytöltä. Ehdotukset muuttuvat rutiiniesi perusteella. Alimmalla rivillä olevat sovellukset siirretään uuteen kansioon."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Näytä sovellusehdotuksia"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Ei kiitos"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Asetukset"</string>
diff --git a/quickstep/res/values-fr-rCA/strings.xml b/quickstep/res/values-fr-rCA/strings.xml
index f92d55c..6199a14 100644
--- a/quickstep/res/values-fr-rCA/strings.xml
+++ b/quickstep/res/values-fr-rCA/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Retrouvez des suggestions d\'applications dans la rangée des favoris de votre écran d\'accueil"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Accédez facilement aux applications que vous utilisez le plus, directement à l\'écran d\'accueil. Les suggestions changeront en fonction de vos habitudes. Les applications dans la rangée du bas seront déplacées vers votre écran d\'accueil."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Accédez facilement aux applications que vous utilisez le plus, directement à l\'écran d\'accueil. Les suggestions changeront en fonction de vos habitudes. Les applications dans la rangée des favoris seront déplacées vers votre écran d\'accueil."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Accédez facilement aux applications que vous utilisez le plus, directement à l\'écran d\'accueil. Les suggestions changeront en fonction de vos habitudes. Les applications dans la rangée du bas seront déplacées vers un nouveau dossier."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Obtenir des suggestions d\'applications"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Non merci"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Paramètres"</string>
diff --git a/quickstep/res/values-fr/strings.xml b/quickstep/res/values-fr/strings.xml
index 9ffa0cc..c66da27 100644
--- a/quickstep/res/values-fr/strings.xml
+++ b/quickstep/res/values-fr/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Retrouvez des suggestions d\'applications dans la zone des favoris de votre écran d\'accueil"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Les suggestions d\'applications permettent d\'afficher vos applications favorites au bas de votre écran d\'accueil. Elles s\'adaptent à vos habitudes d\'utilisation. Les icônes auparavant affichées au bas de l\'écran seront déplacées vers le haut."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Accédez facilement aux applications dont vous vous servez le plus, directement depuis l\'écran d\'accueil. Ces suggestions peuvent varier en fonction de vos habitudes d\'utilisation. Les applications de la zone des favoris seront transférées sur votre écran d\'accueil."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Les suggestions d\'applications permettent d\'afficher vos applications favorites au bas de votre écran d\'accueil. Elles s\'adaptent à vos habitudes d\'utilisation. Les icônes auparavant affichées au bas de l\'écran seront placées dans un nouveau dossier."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Activer les suggestions"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Non, merci"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Paramètres"</string>
diff --git a/quickstep/res/values-gl/strings.xml b/quickstep/res/values-gl/strings.xml
index bea23b7..9946f17 100644
--- a/quickstep/res/values-gl/strings.xml
+++ b/quickstep/res/values-gl/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Recibe suxestións de aplicacións na fila de Favoritos da pantalla de inicio"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Accede facilmente desde a pantalla de inicio ás aplicacións que máis usas. As suxestións irán cambiando en función das túas rutinas. As aplicacións da fila inferior pasarán á pantalla de inicio."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Accede facilmente desde a pantalla de inicio ás aplicacións que máis usas. As suxestións irán cambiando en función das túas rutinas. As aplicacións da fila de Favoritos moveranse á túa pantalla de inicio."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Accede facilmente desde a pantalla de inicio ás aplicacións que máis usas. As suxestións irán cambiando en función das túas rutinas. As aplicacións da fila inferior pasarán a un cartafol novo."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Recibir suxestións de aplicacións"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Non, grazas"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Configuración"</string>
diff --git a/quickstep/res/values-gu/strings.xml b/quickstep/res/values-gu/strings.xml
index a146b24..3439410 100644
--- a/quickstep/res/values-gu/strings.xml
+++ b/quickstep/res/values-gu/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"તમારી હોમ સ્ક્રીનની મનપસંદ પંક્તિમાં ઍપના સુઝાવો મેળવો"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"તમારી સૌથી વધુ વપરાતી ઍપને સીધી હોમ સ્ક્રીન પરથી જ સરળતાથી ઍક્સેસ કરો. સૂચનો તમારા રૂટિનના આધારે બદલાશે. નીચેની પંક્તિમાં રહેલી ઍપ તમારી હોમ સ્ક્રીન પર ખસેડાશે."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"તમારી સૌથી વધુ વપરાતી ઍપને સીધી હોમ સ્ક્રીન પરથી જ સરળતાથી ઍક્સેસ કરો. સૂચનો તમારા રૂટિનના આધારે બદલાશે. મનપસંદ પંક્તિમાં રહેલી ઍપ તમારી હોમ સ્ક્રીન પર ખસેડાશે."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"તમારી સૌથી વધુ વપરાતી ઍપને સીધી હોમ સ્ક્રીન પરથી જ સરળતાથી ઍક્સેસ કરો. સૂચનો તમારા રૂટિનના આધારે બદલાશે. નીચેની પંક્તિમાં રહેલી ઍપ નવા ફોલ્ડરમાં ખસેડાશે."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"ઍપ અંગેના સુઝાવો મેળવો"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"ના, આભાર"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"સેટિંગ"</string>
diff --git a/quickstep/res/values-hi/strings.xml b/quickstep/res/values-hi/strings.xml
index c938e6b..be5a277 100644
--- a/quickstep/res/values-hi/strings.xml
+++ b/quickstep/res/values-hi/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"अपनी होम स्क्रीन की सबसे नीचे वाली पंक्ति में पसंदीदा ऐप्लिकेशन के सुझाव पाएं"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"आपके ज़्यादातर इस्तेमाल किए जाने वाले ऐप्लिकेशन, सीधा अपनी होम स्क्रीन पर पाएं. ऐप्लिकेशन इस्तेमाल करने के आपके रूटीन के हिसाब से सुझाव बदलते रहते हैं. नीचे की पंक्ति के ऐप्लिकेशन होम स्क्रीन पर आ जाएंगे."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"सबसे ज़्यादा इस्तेमाल होने वाले ऐप्लिकेशन सीधे होम स्क्रीन पर देखें. आप ऐप्लिकेशन का कितना इस्तेमाल कर रहे हैं, उसके हिसाब से सुझाव बदलते रहते हैं. आपके पसंदीदा ऐप्लिकेशन, होम स्क्रीन पर नीचे की पंक्ति में दिखाई देंगे."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"सबसे ज़्यादा इस्तेमाल होने वाले ऐप्लिकेशन, सीधे होम स्क्रीन पर पाएं. आपके ऐप्लिकेशन इस्तेमाल करने के रूटीन के हिसाब से सुझाव बदलते रहते हैं. नीचे की पंक्ति के ऐप्लिकेशन एक नए फ़ोल्डर में चले जाएंगे."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"ऐप्लिकेशन के बारे में सुझाव पाएं"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"रहने दें"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"सेटिंग"</string>
diff --git a/quickstep/res/values-hr/strings.xml b/quickstep/res/values-hr/strings.xml
index 640d7cc..620ba51 100644
--- a/quickstep/res/values-hr/strings.xml
+++ b/quickstep/res/values-hr/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Primajte prijedloge aplikacija u retku omiljenih na početnom zaslonu"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Lako pristupite najčešće upotrebljavanim aplikacijama s početnog zaslona. Prijedlozi će se mijenjati na temelju vaših rutina. Aplikacije iz donjeg retka pomaknut će se na početni zaslon."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Lako pristupite najčešće upotrebljavanim aplikacijama s početnog zaslona. Prijedlozi će se mijenjati na temelju vaših rutina. Aplikacije koje se nalaze u retku omiljenih pomaknut će se na početni zaslon."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Lako pristupite najčešće upotrebljavanim aplikacijama s početnog zaslona. Prijedlozi će se mijenjati na temelju vaših rutina. Aplikacije iz donjeg retka pomaknut će se u novu mapu."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Predloži mi aplikacije"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Ne, hvala"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Postavke"</string>
diff --git a/quickstep/res/values-hu/strings.xml b/quickstep/res/values-hu/strings.xml
index 6369293..61f0a3b 100644
--- a/quickstep/res/values-hu/strings.xml
+++ b/quickstep/res/values-hu/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Alkalmazásjavaslatokat kaphat a kezdőképernyőn megjelenő kedvencek sorában"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"A kezdőképernyőről könnyedén hozzáférhet a leggyakrabban használt alkalmazásokhoz. A javaslatok a rutinjai alapján változni fognak. Az alsó sorban lévő alkalmazások felkerülnek a kezdőképernyőre."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"A kezdőképernyőről könnyedén hozzáférhet a leggyakrabban használt alkalmazásokhoz. A javaslatok a rutinjai alapján változnak majd. A kedvencek sorában lévő alkalmazások a kezdőképernyőre kerülnek."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"A kezdőképernyőről könnyedén hozzáférhet a leggyakrabban használt alkalmazásokhoz. A javaslatok a rutinjai alapján változni fognak. Az alsó sorban lévő alkalmazások egy új mappába kerülnek."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Kérek javaslatokat"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Köszönöm, nem"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Beállítások"</string>
diff --git a/quickstep/res/values-hy/strings.xml b/quickstep/res/values-hy/strings.xml
index 9eeaa98..0f9356a 100644
--- a/quickstep/res/values-hy/strings.xml
+++ b/quickstep/res/values-hy/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Ստացեք հավելվածների առաջարկներ հիմնական էկրանի «Ընտրանի» տողում"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Անմիջապես հիմնական էկրանից բացեք հաճախ օգտագործվող հավելվածները։ Առաջարկվող հավելվածները կփոփոխվեն՝ կախված ձեր գործողություններից։ Ներքևի տողի հավելվածները կտեղափոխվեն վերև հիմնական էկրանին։"</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Արագ բացեք հաճախ օգտագործվող հավելվածներն անմիջապես հիմնական էկրանից։ Առաջարկները կփոփոխվեն՝ կախված ձեր գործողություններից։ «Ընտրանի» տողի հավելվածները կտեղափոխվեն հիմնական էկրան։"</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Արագ բացեք հաճախ օգտագործվող հավելվածներն անմիջապես հիմնական էկրանից։ Առաջարկները կփոփոխվեն՝ կախված ձեր գործողություններից։ Ներքևում ցուցադրվող հավելվածները կտեղափոխվեն նոր պանակ։"</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Ստանալ առաջարկներ"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Ոչ, շնորհակալություն"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Կարգավորումներ"</string>
diff --git a/quickstep/res/values-in/strings.xml b/quickstep/res/values-in/strings.xml
index 14d8c3e..b95871f 100644
--- a/quickstep/res/values-in/strings.xml
+++ b/quickstep/res/values-in/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Dapatkan saran aplikasi di baris favorit Layar utama"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Akses aplikasi yang paling sering digunakan dengan mudah, langsung di Layar utama. Saran akan berubah berdasarkan rutinitas Anda. Aplikasi di baris paling bawah akan berpindah naik ke Layar utama."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Mudah mengakses aplikasi yang paling sering digunakan, langsung di Layar utama. Saran akan berubah berdasarkan rutinitas Anda. Aplikasi di baris favorit akan berpindah ke Layar utama."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Akses aplikasi yang paling sering digunakan dengan mudah, langsung di Layar utama. Saran akan berubah berdasarkan rutinitas Anda. Aplikasi di baris paling bawah akan berpindah ke folder baru."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Dapatkan saran aplikasi"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Lain kali"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Setelan"</string>
diff --git a/quickstep/res/values-is/strings.xml b/quickstep/res/values-is/strings.xml
index 1761203..5730900 100644
--- a/quickstep/res/values-is/strings.xml
+++ b/quickstep/res/values-is/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Fáðu tillögur að forritum á eftirlætissvæði heimaskjásins"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Nálgastu forritin sem þú notar mest auðveldlega á heimaskjánum. Tillögurnar breytast í samræmi við notkun þína. Forrit í neðstu röð færast upp á heimaskjáinn."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Nálgastu forritin sem þú notar mest á einfaldan hátt á heimaskjánum. Tillögurnar breytast í samræmi við notkun þína. Forrit á eftirlætissvæði færast á heimaskjáinn."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Nálgastu forritin sem þú notar mest auðveldlega á heimaskjánum. Tillögurnar breytast í samræmi við notkun þína. Forrit í neðstu röð færast í nýja möppu."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Fá tillögur að forritum"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Nei, takk"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Stillingar"</string>
diff --git a/quickstep/res/values-it/strings.xml b/quickstep/res/values-it/strings.xml
index 874d072..7e04cf6 100644
--- a/quickstep/res/values-it/strings.xml
+++ b/quickstep/res/values-it/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Visualizza app suggerite nella riga dei Preferiti della schermata Home"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Accedi facilmente alle app più utilizzate direttamente dalla schermata Home. I suggerimenti varieranno in base alle tue routine. Le app nella riga inferiore verranno spostate più in alto sulla schermata Home."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Accedi facilmente alle app più utilizzate direttamente dalla schermata Home. I suggerimenti varieranno in base alle tue routine. Le app nella riga dei Preferiti verranno spostate nella schermata Home."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Accedi facilmente alle app più utilizzate direttamente dalla schermata Home. I suggerimenti varieranno in base alle tue routine. Le app nella riga inferiore verranno spostate in una nuova cartella."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Visualizza app suggerite"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"No, grazie"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Impostazioni"</string>
diff --git a/quickstep/res/values-iw/strings.xml b/quickstep/res/values-iw/strings.xml
index 09d76f8..56148e7 100644
--- a/quickstep/res/values-iw/strings.xml
+++ b/quickstep/res/values-iw/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"קבלת הצעות לאפליקציות בשורת המועדפות של מסך הבית"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"גישה נוחה לאפליקציות שנעשה בהן שימוש תכוף – ישירות ממסך הבית. ההצעות ישתנו בהתאם להרגלי השימוש שלך. אפליקציות שמופיעות בשורה התחתונה יעברו למעלה למסך הבית."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"גישה נוחה לאפליקציות שהשתמשת בהן הכי הרבה, ישירות ממסך הבית. ההצעות ישתנו בהתאם להרגלי השימוש שלך. אפליקציות בשורת המועדפות יועברו למסך הבית."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"גישה נוחה לאפליקציות שנעשה בהן שימוש תכוף – ישירות ממסך הבית. ההצעות ישתנו בהתאם להרגלי השימוש שלך. אפליקציות שמופיעות בשורה התחתונה יעברו לתיקייה חדשה."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"קבלת הצעות לאפליקציות"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"לא, תודה"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"הגדרות"</string>
diff --git a/quickstep/res/values-ja/strings.xml b/quickstep/res/values-ja/strings.xml
index 50c031e..013939f 100644
--- a/quickstep/res/values-ja/strings.xml
+++ b/quickstep/res/values-ja/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"ホーム画面のお気に入りの行でアプリの候補を利用できます"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"ホーム画面で、使用頻度の高いアプリに簡単にアクセスできるようになります。アプリの候補はルーティンに応じて変わります。ホーム画面で今一番下の行にあるアプリは、一行上に移動します。"</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"ホーム画面で、使用頻度の高いアプリに簡単にアクセスできるようになります。アプリの候補はルーティンに応じて変わります。お気に入りの行にあるアプリがホーム画面に移動します。"</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"ホーム画面で、使用頻度の高いアプリに簡単にアクセスできるようになります。アプリの候補はルーティンに応じて変わります。一番下の行にあるアプリが新しいフォルダに移動します。"</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"アプリの候補を利用"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"使用しない"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"設定"</string>
diff --git a/quickstep/res/values-ka/strings.xml b/quickstep/res/values-ka/strings.xml
index 2bc0108..d54ac881c 100644
--- a/quickstep/res/values-ka/strings.xml
+++ b/quickstep/res/values-ka/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"მიიღეთ აპების შემოთავაზებები მთავარი ეკრანის რჩეულების მწკრივში"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"მარტივად იქონიეთ ყველაზე ხშირად გამოყენებულ აპებზე წვდომა მთავარი ეკრანიდან. შეთავაზებები შეიცვლება თქვენი რუტინების მიხედვით. მოხდება ქვედა რიგში არსებული აპების მთავარ ეკრანზე გადატანა."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"მარტივად იქონიეთ წვდომა ყველაზე ხშირად გამოყენებულ აპებზე მთავარი ეკრანიდან. შეთავაზებები შეიცვლება თქვენი რუტინების მიხედვით. რჩეულების მწკრივში არსებული აპები თქვენს მთავარ ეკრანზე გადავა."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"მარტივად იქონიეთ ყველაზე ხშირად გამოყენებულ აპებზე წვდომა მთავარი ეკრანიდან. შეთავაზებები შეიცვლება თქვენი რუტინების მიხედვით. მოხდება ქვედა რიგში არსებული აპების ახალ საქაღალდეში გადატანა."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"აპის შეთავაზებების მიღება"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"არა, გმადლობთ"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"პარამეტრები"</string>
diff --git a/quickstep/res/values-kk/strings.xml b/quickstep/res/values-kk/strings.xml
index 7758125..bfa2a5c 100644
--- a/quickstep/res/values-kk/strings.xml
+++ b/quickstep/res/values-kk/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Қолданба ұсыныстары негізгі экрандағы таңдаулылар жолында көрсетілетін болады"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Жиі пайдаланылатын қолданбаларға негізгі экраннан кіруге болады. Ұсыныстар күнделікті әрекеттеріңізге сәйкес өзгереді. Төменгі қатардағы қолданбалар негізгі экранға қарай жоғары жылжиды."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Жиі пайдаланылатын қолданбаларға негізгі экраннан оңай кіре аласыз. Ұсыныстар күнделікті әрекеттеріңізге сәйкес өзгереді. Таңдаулылар жолындағы қолданбалар негізгі экранға ауысады."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Жиі пайдаланылатын қолданбаларға негізгі экраннан кіруге болады. Ұсыныстар күнделікті әрекеттеріңізге сәйкес өзгереді. Төменгі қатардағы қолданбалар жаңа қалтаға жылжиды."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Қолданба ұсыныстарын алу"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Жоқ, рақмет"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Параметрлер"</string>
diff --git a/quickstep/res/values-km/strings.xml b/quickstep/res/values-km/strings.xml
index 93b2361..4a78d9d 100644
--- a/quickstep/res/values-km/strings.xml
+++ b/quickstep/res/values-km/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"ទទួលបាន​ការណែនាំកម្មវិធី​នៅលើ​ជួរដេកសំណព្វ​នៃអេក្រង់ដើម​របស់អ្នក"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"ចូលប្រើ​កម្មវិធី​ដែលអ្នកប្រើ​ញឹកញាប់បំផុត​បានយ៉ាង​ងាយស្រួល​នៅលើ​អេក្រង់ដើម​ផ្ទាល់។ ការណែនាំ​នឹងប្រែប្រួល​ទៅតាម​ទម្លាប់​របស់អ្នក។ កម្មវិធី​នៅជួរ​ខាងក្រោម​នឹងផ្លាស់ទីឡើង​ទៅអេក្រង់ដើម​របស់អ្នក។"</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"ចូលប្រើ​កម្មវិធី​ដែលអ្នកប្រើ​ញឹកញាប់បំផុត​បានយ៉ាង​ងាយស្រួល​នៅលើ​អេក្រង់ដើមដោយផ្ទាល់។ ការណែនាំ​នឹងប្រែប្រួល​ទៅតាម​ទម្លាប់​របស់អ្នក។ កម្មវិធី​នៅក្នុង​ជួរដេក​សំណព្វ​នឹងផ្លាស់ទី​ទៅអេក្រង់ដើម​របស់អ្នក។"</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"ចូលប្រើ​កម្មវិធី​ដែលអ្នកប្រើ​ញឹកញាប់បំផុត​បានយ៉ាង​ងាយស្រួល​នៅលើ​អេក្រង់ដើម​ផ្ទាល់។ ការណែនាំ​នឹងប្រែប្រួល​ទៅតាម​ទម្លាប់​របស់អ្នក។ កម្មវិធី​នៅជួរ​ខាងក្រោម​នឹងផ្លាស់ទី​ទៅថតថ្មី។"</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"ទទួលការណែនាំ​កម្មវិធី"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"ទេ អរគុណ"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"ការកំណត់"</string>
diff --git a/quickstep/res/values-kn/strings.xml b/quickstep/res/values-kn/strings.xml
index 2c1f699..91a12f8 100644
--- a/quickstep/res/values-kn/strings.xml
+++ b/quickstep/res/values-kn/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"ನಿಮ್ಮ ಹೋಮ್ ಸ್ಕ್ರೀನ್‌ನ ಮೆಚ್ಚಿನವುಗಳ ಸಾಲಿನಲ್ಲಿ ಆ್ಯಪ್ ಸಲಹೆಗಳನ್ನು ಪಡೆಯಿರಿ"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"ನೀವು ಹೆಚ್ಚು ಬಳಸಿದ ಆ್ಯಪ್‌ಗಳನ್ನು ಹೋಮ್ ಸ್ಕ್ರೀನ್‌ನಲ್ಲಿಯೇ ಸುಲಭವಾಗಿ ಪ್ರವೇಶಿಸಿ. ನಿಮ್ಮ ದಿನಚರಿಯನ್ನು ಆಧರಿಸಿ ಸಲಹೆಗಳು ಬದಲಾಗುತ್ತವೆ. ಕೆಳಭಾಗದ ಸಾಲಿನಲ್ಲಿನ ಆ್ಯಪ್‌ಗಳು ನಿಮ್ಮ ಹೋಮ್ ಸ್ಕ್ರೀನ್‌ ಕಡೆಗೆ ಚಲಿಸುತ್ತವೆ."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"ನೀವು ಹೆಚ್ಚು ಬಳಸಿದ ಆ್ಯಪ್‌ಗಳನ್ನು ಹೋಮ್ ಸ್ಕ್ರೀನ್‌ನಲ್ಲಿಯೇ ಸುಲಭವಾಗಿ ಪ್ರವೇಶಿಸಿ. ನಿಮ್ಮ ದಿನಚರಿಯನ್ನು ಆಧರಿಸಿ ಸಲಹೆಗಳು ಬದಲಾಗುತ್ತವೆ. ಮೆಚ್ಚಿನವುಗಳ ಸಾಲಿನಲ್ಲಿನ ಆ್ಯಪ್‌ಗಳು ನಿಮ್ಮ ಹೋಮ್ ಸ್ಕ್ರೀನ್‌ಗೆ ಚಲಿಸುತ್ತವೆ."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"ನೀವು ಹೆಚ್ಚು ಬಳಸಿದ ಆ್ಯಪ್‌ಗಳನ್ನು ಹೋಮ್ ಸ್ಕ್ರೀನ್‌ನಲ್ಲಿಯೇ ಸುಲಭವಾಗಿ ಪ್ರವೇಶಿಸಿ. ನಿಮ್ಮ ದಿನಚರಿಯನ್ನು ಆಧರಿಸಿ ಸಲಹೆಗಳು ಬದಲಾಗುತ್ತವೆ. ಕೆಳಭಾಗದ ಸಾಲಿನಲ್ಲಿನ ಆ್ಯಪ್‌ಗಳು ಹೊಸ ಫೋಲ್ಡರ್‌ಗೆ ಚಲಿಸುತ್ತವೆ."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"ಆ್ಯಪ್ ಸಲಹೆಗಳನ್ನು ಪಡೆಯಿರಿ"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"ಬೇಡ"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
diff --git a/quickstep/res/values-ko/strings.xml b/quickstep/res/values-ko/strings.xml
index 6626bc1..bfe98f9 100644
--- a/quickstep/res/values-ko/strings.xml
+++ b/quickstep/res/values-ko/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"홈 화면의 즐겨찾기 행에서 앱 제안 보기"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"홈 화면에서 자주 사용하는 앱에 바로 액세스할 수 있습니다. 제안은 사용 습관에 따라 바뀌며, 하단의 앱들은 홈 화면으로 이동합니다."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"홈 화면에서 가장 많이 사용한 앱에 바로 액세스할 수 있습니다. 제안은 루틴에 따라 달라집니다. 즐겨찾기 행의 앱이 홈 화면으로 이동합니다."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"홈 화면에서 자주 사용하는 앱에 바로 액세스할 수 있습니다. 제안은 사용 습관에 따라 바뀌며, 하단의 앱들은 새 폴더로 이동합니다."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"앱 제안받기"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"나중에"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"설정"</string>
diff --git a/quickstep/res/values-ky/strings.xml b/quickstep/res/values-ky/strings.xml
index dac4d1d..5472ead 100644
--- a/quickstep/res/values-ky/strings.xml
+++ b/quickstep/res/values-ky/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Сунушталган колдонмолор башкы экрандагы тандалмалардын катарында көрүнөт."</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Көп колдонулган колдонмолор башкы экранда жайгашып, алардын тизмеси маал-маалы менен өзгөрүп турат. Ылдый жакта жайгашкан тилкедеги колдонмолор башкы экранга жылдырылат."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Көп иштетилген колдонмолорго Башкы экрандан оңой кириңиз. Сунуштар тартиптин негизинде өзгөрөт. Тандалмалардын катарындагы колдонмолор башкы экраныңызга жылдырылат."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Көп колдонулган колдонмолор башкы экранда жайгашып, алардын тизмеси маал-маалы менен өзгөрүп турат. Ылдый жакта жайгашкан тилкедеги колдонмолор жаңы папкага жылдырылат."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Сунушталган колдонолорду алуу"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Жок, рахмат"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Жөндөөлөр"</string>
diff --git a/quickstep/res/values-land/dimens.xml b/quickstep/res/values-land/dimens.xml
index f233bde..905fbda 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>
@@ -73,4 +74,10 @@
 
     <!-- Gesture Tutorial mock taskbar -->
     <dimen name="gesture_tutorial_taskbar_padding_start_end">218dp</dimen>
+
+    <!--  Taskbar 3 button spacing  -->
+    <dimen name="taskbar_button_margin_5_5">94.5dp</dimen>
+    <dimen name="taskbar_button_margin_6_5">219.6dp</dimen>
+    <dimen name="taskbar_button_margin_4_5">84dp</dimen>
+    <dimen name="taskbar_button_margin_4_4">79dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/quickstep/res/values-lo/strings.xml b/quickstep/res/values-lo/strings.xml
index f3c3cbd..7d87ec4 100644
--- a/quickstep/res/values-lo/strings.xml
+++ b/quickstep/res/values-lo/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"ຮັບການແນະນຳແອັບຢູ່ແຖວລາຍການທີ່ມັກຂອງໜ້າຈໍຫຼັກຂອງທ່ານ"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"ເຂົ້າເຖິງແອັບທີ່ທ່ານໃຊ້ຫຼາຍທີ່ສຸດໄດ້ຢ່າງງ່າຍດາຍທັນທີຈາກໜ້າຈໍຫຼັກ. ການແນະນຳຈະປ່ຽນແປງຕາມການນຳໃຊ້ປະຈຳຂອງທ່ານ. ແອັບຢູ່ແຖວລຸ່ມສຸດຈະຍ້າຍຂຶ້ນໄປໃສ່ໜ້າຈໍຫຼັກຂອງທ່ານ."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"ເຂົ້າເຖິງແອັບທີ່ທ່ານໃຊ້ຫຼາຍທີ່ສຸດໄດ້ຢ່າງງ່າຍດາຍທັນທີຈາກໜ້າຈໍຫຼັກ. ການແນະນຳຈະປ່ຽນແປງຕາມການນຳໃຊ້ປະຈຳຂອງທ່ານ. ຕອນນີ້ແອັບໃນລາຍການທີ່ມັກຈະຍ້າຍໄປໃສ່ໜ້າຈໍຫຼັກຂອງທ່ານ."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"ເຂົ້າເຖິງແອັບທີ່ທ່ານໃຊ້ຫຼາຍທີ່ສຸດໄດ້ຢ່າງງ່າຍດາຍທັນທີຈາກໜ້າຈໍຫຼັກ. ການແນະນຳຈະປ່ຽນແປງຕາມການນຳໃຊ້ປະຈຳຂອງທ່ານ. ແອັບຢູ່ແຖວລຸ່ມສຸດຈະຍ້າຍໄປໂຟນເດີໃໝ່."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"ຮັບການແນະນຳແອັບ"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"ບໍ່, ຂອບໃຈ"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"ການຕັ້ງຄ່າ"</string>
diff --git a/quickstep/res/values-lt/strings.xml b/quickstep/res/values-lt/strings.xml
index 25a84a7..b60146f 100644
--- a/quickstep/res/values-lt/strings.xml
+++ b/quickstep/res/values-lt/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Gaukite programų pasiūlymų pagrindinio ekrano eilutėje „Mėgstamiausios“"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Lengvai pasiekite dažniausiai naudojamas programas iškart pagrindiniame ekrane. Pasiūlymai keisis atsižvelgiant į tai, kaip jas naudojate. Apatinėje eilutėje esančios programos bus perkeltos į pagrindinį ekraną."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Lengvai pasiekite dažniausiai naudojamas programas iškart pagrindiniame ekrane. Pasiūlymai keisis atsižvelgiant į tai, kaip jas naudojate. Eilutėje „Mėgstamiausios“ rodomos programos bus perkeltos į pagrindinį ekraną."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Lengvai pasiekite dažniausiai naudojamas programas iškart pagrindiniame ekrane. Pasiūlymai keisis atsižvelgiant į tai, kaip jas naudojate. Apatinėje eilutėje esančios programos bus perkeltos į naują aplanką."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Gauti programų pasiūlymų"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Ne, ačiū"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Nustatymai"</string>
diff --git a/quickstep/res/values-lv/strings.xml b/quickstep/res/values-lv/strings.xml
index ee25cb5..d3c3b80 100644
--- a/quickstep/res/values-lv/strings.xml
+++ b/quickstep/res/values-lv/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Saņemiet lietotņu ieteikumus izlases rindā sākuma ekrānā"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Varat sākuma ekrānā ērti piekļūt savām visbiežāk izmantotajām lietotnēm. Ieteikumi mainīsies atkarībā no jūsu paradumiem. Apakšējā rindā esošās lietotnes tiks pārvietotas uz augšu — uz sākuma ekrānu."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Varat sākuma ekrānā ērti piekļūt savām visbiežāk izmantotajām lietotnēm. Ieteikumi mainīsies atkarībā no jūsu paradumiem. Lietotnes no izlases rindas tiks pārvietotas uz sākuma ekrānu."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Varat sākuma ekrānā ērti piekļūt savām visbiežāk izmantotajām lietotnēm. Ieteikumi mainīsies atkarībā no jūsu paradumiem. Apakšējā rindā esošās lietotnes tiks pārvietotas uz jaunu mapi."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Rādīt ieteicamās lietotnes"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Nē, paldies"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Iestatījumi"</string>
diff --git a/quickstep/res/values-mk/strings.xml b/quickstep/res/values-mk/strings.xml
index 75f0933..7ac9ad0 100644
--- a/quickstep/res/values-mk/strings.xml
+++ b/quickstep/res/values-mk/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Добивајте предлози за апликации во редот со омилени на почетниот екран"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Лесно пристапувајте до најкористените апликации директно на почетниот екран. Предлозите ќе се менуваат според рутините. Апликациите од последниот ред ќе се поместуваат нагоре до почетниот екран."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Лесно пристапувајте до најкористените апликации на почетниот екран. Предлозите ќе се менуваат според рутините. Апликациите од редот со омилени ќе се преместат на почетниот екран."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Лесно пристапувајте до најкористените апликации директно на почетниот екран. Предлозите ќе се менуваат според рутините. Апликациите од последниот ред ќе се преместуваат во нова папка."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Добивајте предлози за апликации"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Не, фала"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Поставки"</string>
diff --git a/quickstep/res/values-ml/strings.xml b/quickstep/res/values-ml/strings.xml
index 9f00e91..90fc513 100644
--- a/quickstep/res/values-ml/strings.xml
+++ b/quickstep/res/values-ml/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"നിങ്ങളുടെ ഹോം സ്‌ക്രീനിന്റെ \'പ്രിയപ്പെട്ടവ\' വരിയിൽ ആപ്പ് നിർദ്ദേശങ്ങൾ നേടുക"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"നിങ്ങൾ ഏറ്റവും കൂടുതൽ ഉപയോഗിച്ച ആപ്പുകൾ ഹോം സ്ക്രീനിൽ നിന്ന് തന്നെ എളുപ്പത്തിൽ ആക്‌സസ് ചെയ്യൂ. നിങ്ങളുടെ ദിനചര്യകളുടെ അടിസ്ഥാനത്തിൽ നിർദ്ദേശങ്ങൾ മാറും. താഴത്തെ നിരയിലുള്ള ആപ്പുകൾ നിങ്ങളുടെ ഹോം സ്‌ക്രീനിലേക്ക് നീങ്ങും."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"നിങ്ങൾ ഏറ്റവും കൂടുതൽ ഉപയോഗിച്ച ആപ്പുകൾ ഹോം സ്ക്രീനിൽ നിന്ന് തന്നെ എളുപ്പത്തിൽ ആക്‌സസ് ചെയ്യൂ. നിങ്ങളുടെ ദിനചര്യകളുടെ അടിസ്ഥാനത്തിൽ നിർദ്ദേശങ്ങൾ മാറും. \'പ്രിയപ്പെട്ടവ\' വരിയിലുള്ള ആപ്പുകൾ നിങ്ങളുടെ ഹോം സ്‌ക്രീനിലേക്ക് നീങ്ങും."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"നിങ്ങൾ ഏറ്റവും കൂടുതൽ ഉപയോഗിച്ച ആപ്പുകൾ ഹോം സ്ക്രീനിൽ നിന്ന് തന്നെ എളുപ്പത്തിൽ ആക്‌സസ് ചെയ്യൂ. നിങ്ങളുടെ ദിനചര്യകളുടെ അടിസ്ഥാനത്തിൽ നിർദ്ദേശങ്ങൾ മാറും. താഴത്തെ നിരയിലുള്ള ആപ്പുകൾ പുതിയൊരു ഫോൾഡറിലേക്ക് നീങ്ങും."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"ആപ്പ് നിർദ്ദേശങ്ങൾ നേടുക"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"വേണ്ട"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"ക്രമീകരണം"</string>
diff --git a/quickstep/res/values-mn/strings.xml b/quickstep/res/values-mn/strings.xml
index 59728a6..c435528 100644
--- a/quickstep/res/values-mn/strings.xml
+++ b/quickstep/res/values-mn/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Үндсэн нүүрний дуртай мөрнөөсөө санал болгож буй аппуудыг аваарай"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Хамгийн их ашигладаг аппууддаа Үндсэн нүүрнээс хялбархан хандаарай. Санал болгож буй аппуудыг таны хэвшлээс хамаарч өөрчилнө. Доод мөрд буй аппуудыг таны Үндсэн нүүр лүү дээш зөөнө."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Хамгийн их ашигладаг аппууддаа Үндсэн нүүрнээсээ хялбархан хандаарай. Санал болголтыг таны хэвшлээс хамааран өөрчилнө. Дуртай мөрөнд буй аппуудыг таны үндсэн нүүр лүү зөөнө."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Хамгийн их ашигладаг аппууддаа Үндсэн нүүрнээс хялбархан хандаарай. Санал болгож буй аппуудыг таны хэвшлээс хамаарч өөрчилнө. Доод мөрөнд буй аппуудыг шинэ фолдер луу зөөнө."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Санал болгож буй аппуудыг авах"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Үгүй, баярлалаа"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Тохиргоо"</string>
diff --git a/quickstep/res/values-mr/strings.xml b/quickstep/res/values-mr/strings.xml
index 5f2fb0c..fc90271 100644
--- a/quickstep/res/values-mr/strings.xml
+++ b/quickstep/res/values-mr/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"तुमच्या होम स्क्रीनच्या पसंतीच्या पंक्तीवर अ‍ॅप सूचना मिळवा"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"तुमची सर्वाधिक वापरली जाणारी अ‍ॅप्स होम स्क्रीनवरच सहजपणे अ‍ॅक्सेस करा. तुमच्या दिनक्रमानुसार तुम्हाला मिळणाऱ्या सूचना बदलतील. तळाशी असणारी अ‍ॅप्स तुमच्या होम स्क्रीनवर जातील."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"तुमची सर्वाधिक वापरली जाणारी अ‍ॅप्स होम स्क्रीनवर सहजपणे अ‍ॅक्सेस करा. सूचना तुमच्या दिनक्रमांनुसार बदलतील. पसंतीच्या पंक्तीमधील अ‍ॅप्स तुमच्या होम स्क्रीनवर हलवली जातील."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"तुमची सर्वाधिक वापरली जाणारी अ‍ॅप्स होम स्क्रीनवरच सहजपणे अ‍ॅक्सेस करा. सूचना तुमच्या दिनक्रमांच्या आधारावर बदलतील. तळाच्या पंक्तीवरील अ‍ॅप्स नवीन फोल्डरवर जातील."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"अ‍ॅप सूचना मिळवा"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"नाही, नको"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"सेटिंग्ज"</string>
diff --git a/quickstep/res/values-ms/strings.xml b/quickstep/res/values-ms/strings.xml
index 27dcc41..5d3f4a8 100644
--- a/quickstep/res/values-ms/strings.xml
+++ b/quickstep/res/values-ms/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Dapatkan cadangan apl di baris kegemaran Skrin utama anda"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Akses apl yang paling kerap anda gunakan dengan mudah pada Skrin utama. Cadangan akan berubah berdasarkan rutin anda. Apl di baris bawah akan beralih ke atas, ke Skrin utama anda."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Akses apl yang paling kerap anda gunakan dengan mudah pada Skrin utama. Cadangan akan berubah berdasarkan rutin anda. Apl di baris kegemaran akan beralih ke Skrin utama anda."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Akses apl yang paling kerap anda gunakan dengan mudah, terus pada Skrin utama. Cadangan akan berubah berdasarkan rutin anda. Apl di baris bawah akan beralih ke folder baharu."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Dapatkan cadangan apl"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Tidak perlu"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Tetapan"</string>
diff --git a/quickstep/res/values-my/strings.xml b/quickstep/res/values-my/strings.xml
index 825b6f2..8f6af71 100644
--- a/quickstep/res/values-my/strings.xml
+++ b/quickstep/res/values-my/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"သင်၏ \'ပင်မစာမျက်နှာ\' ၏ အနှစ်သက်ဆုံးများအတန်းတွင် အက်ပ်အကြံပြုချက်များ ရယူခြင်း"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"အသုံးအများဆုံးအက်ပ်များကို \'ပင်မစာမျက်နှာ\' တွင် အလွယ်တကူ ဖွင့်နိုင်သည်။ သင်၏ ပုံမှန်လုပ်ဆောင်ချက်များပေါ် အခြေခံ၍ အကြံပြုချက်များ ပြောင်းလဲပါမည်။ အောက်ခြေအတန်းရှိ အက်ပ်များကို သင်၏ \'ပင်မစာမျက်နှာ\' သို့ရွှေ့လိုက်မည်။"</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"အသုံးအများဆုံးအက်ပ်များကို \'ပင်မစာမျက်နှာ\' တွင် အလွယ်တကူ သုံးနိုင်သည်။ သင်၏ ပုံမှန်အစီအစဉ်များပေါ် အခြေခံ၍ အကြံပြုချက်များ ပြောင်းလဲပါမည်။ အနှစ်သက်ဆုံးများအတန်းရှိ အက်ပ်များကို သင်၏ \'ပင်မစာမျက်နှာ\' သို့ရွှေ့လိုက်မည်။"</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"အသုံးအများဆုံးအက်ပ်များကို ပင်မစာမျက်နှာတွင် အလွယ်တကူ သုံးနိုင်သည်။ သင်၏ ပုံမှန်အစီအစဉ်များပေါ် အခြေခံ၍ အကြံပြုချက်များ ပြောင်းလဲပါမည်။ အောက်ခြေအတန်းရှိ အက်ပ်များကို ဖိုင်တွဲအသစ်သို့ ရွှေ့လိုက်မည်။"</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"အက်ပ်အကြံပြုချက်များ ရယူရန်"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"မလိုပါ"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"ဆက်တင်များ"</string>
diff --git a/quickstep/res/values-nb/strings.xml b/quickstep/res/values-nb/strings.xml
index 250724e..eff2e3c 100644
--- a/quickstep/res/values-nb/strings.xml
+++ b/quickstep/res/values-nb/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Få appforslag i favoritter-raden på startskjermen"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Få enkel tilgang til appene du bruker oftest, rett fra startskjermen. Forslagene endres basert på rutinene dine. Appene i den nederste raden flyttes opp til startskjermen."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Få enkel tilgang til appene du bruker oftest, rett fra startskjermen. Forslagene endres basert på rutinene dine. Apper i favoritter-raden blir flyttet til startskjermen."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Få enkel tilgang til appene du bruker oftest, rett fra startskjermen. Forslagene endres basert på rutinene dine. Appene i den nederste raden flyttes til en ny mappe."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Få appforslag"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Nei takk"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Innstillinger"</string>
diff --git a/quickstep/res/values-ne/strings.xml b/quickstep/res/values-ne/strings.xml
index f7214b0..3a23567 100644
--- a/quickstep/res/values-ne/strings.xml
+++ b/quickstep/res/values-ne/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"आफ्नो होम स्क्रिनको मन पर्ने नामक पङ्क्तिमा एपसम्बन्धी सिफारिस प्राप्त गर्नुहोस्"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"आफूले सबैभन्दा बढी प्रयोग गर्ने एप होम स्क्रिनबाट सजिलै चलाउनुहोस्। सिफारिस गरिने एपहरूको क्रम तपाईंले एप प्रयोग गर्ने समयतालिकाअनुसार बदलिने छ। फेदको रोमा रहेका एपहरू तपाईंको होम स्क्रिनको सिरानमा सर्ने छन्।"</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"आफूले सबैभन्दा बढी प्रयोग गर्ने एपहरू गृह स्क्रिनबाटै सजिलैसँग खोल्नुहोस्। सिफारिस गरिने एपहरूको क्रम तपाईंको दिनचर्याअनुसार बदलिने छ। मन पर्ने नामक पङ्क्तिमा रहेका एपहरू सारेर होम स्क्रिनमा लगिने छन्।"</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"गृह स्क्रिनबाटै आफूले सबैभन्दा बढी प्रयोग गर्ने एप सजिलै चलाउनुहोस्। सिफारिस गरिने एपहरूको क्रम तपाईंले एप प्रयोग गर्ने समयतालिकाअनुसार बदलिने छ। फेदको पङ्क्तिमा रहेका एपहरू एउटा नयाँ फोल्डरमा सर्ने छन्।"</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"एपसम्बन्धी सिफारिस प्राप्त गर्नुहोस्"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"पर्दैन, धन्यवाद"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"सेटिङ"</string>
diff --git a/quickstep/res/values-nl/strings.xml b/quickstep/res/values-nl/strings.xml
index ff5e45e..650e340 100644
--- a/quickstep/res/values-nl/strings.xml
+++ b/quickstep/res/values-nl/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"App-suggesties krijgen op de rij met favorieten op het startscherm"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Open je meestgebruikte apps makkelijk vanaf het startscherm. De suggesties veranderen op basis van je routines. Apps in de onderste rij worden naar je startscherm verplaatst."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Open je meestgebruikte apps makkelijk vanaf het startscherm. De suggesties veranderen op basis van je routines. Apps in de rij met favorieten worden naar het startscherm verplaatst."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Open je meestgebruikte apps makkelijk vanaf het startscherm. De suggesties veranderen op basis van je routines. Apps in de onderste rij worden naar een nieuwe map verplaatst."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"App-suggesties krijgen"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Nee, bedankt"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Instellingen"</string>
diff --git a/quickstep/res/values-or/strings.xml b/quickstep/res/values-or/strings.xml
index d7246c4..5016399 100644
--- a/quickstep/res/values-or/strings.xml
+++ b/quickstep/res/values-or/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"ଆପଣଙ୍କ ହୋମ ସ୍କ୍ରିନର ପସନ୍ଦର ଧାଡ଼ିରେ ଆପ ପରାମର୍ଶଗୁଡ଼ିକ ପାଆନ୍ତୁ"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"ଆପଣଙ୍କର ସବୁଠାରୁ ଅଧିକ-ବ୍ୟବହୃତ ଆପ୍ସକୁ ସିଧା ହୋମ ସ୍କ୍ରିନରେ ସହଜରେ ଆକ୍ସେସ କରନ୍ତୁ। ଆପଣଙ୍କ ରୁଟିନଗୁଡ଼ିକ ଆଧାରରେ ପରାମର୍ଶଗୁଡ଼ିକ ପରିବର୍ତ୍ତିତ ହେବ। ତଳ ଧାଡ଼ିରେ ଥିବା ଆପ୍ସ ଆପଣଙ୍କ ହୋମ ସ୍କ୍ରିନକୁ ମୁଭ ହୋଇଯିବ।"</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"ହୋମ ସ୍କ୍ରିନରେ ହିଁ ଆପଣଙ୍କର ସବୁଠାରୁ ଅଧିକ-ବ୍ୟବହୃତ ଆପ୍ସକୁ ସହଜରେ ଆକ୍ସେସ କରନ୍ତୁ। ଆପଣଙ୍କ ରୁଟିନଗୁଡ଼ିକ ଆଧାରରେ ପରାମର୍ଶଗୁଡ଼ିକ ବଦଳିବ। ଆପଣଙ୍କ ହୋମ ସ୍କ୍ରିନକୁ ପସନ୍ଦର ଧାଡ଼ିରେ ଥିବା ଆପ୍ସ ମୁଭ ହୋଇଯିବ।"</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"ଆପଣଙ୍କର ସବୁଠାରୁ ଅଧିକ-ବ୍ୟବହୃତ ଆପ୍ସକୁ, ସିଧା ହୋମ ସ୍କ୍ରିନରେ ସହଜରେ ଆକ୍ସେସ କରନ୍ତୁ। ଆପଣଙ୍କ ରୁଟିନଗୁଡ଼ିକ ଆଧାରରେ ପରାମର୍ଶଗୁଡ଼ିକ ପରିବର୍ତ୍ତିତ ହେବ। ତଳ ଧାଡ଼ିରେ ଥିବା ଆପ୍ସ ଏକ ନୂଆ ଫୋଲ୍ଡରକୁ ମୁଭ ହୋଇଯିବ।"</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"ଆପ୍ ପରାମର୍ଶଗୁଡ଼ିକ ପାଆନ୍ତୁ"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"ନାହିଁ, ଥାଉ"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"ସେଟିଂସ"</string>
diff --git a/quickstep/res/values-pa/strings.xml b/quickstep/res/values-pa/strings.xml
index 83def85..7d1d2b5 100644
--- a/quickstep/res/values-pa/strings.xml
+++ b/quickstep/res/values-pa/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"ਆਪਣੀ ਹੋਮ ਸਕ੍ਰੀਨ ਦੀ ਮਨਪਸੰਦ ਕਤਾਰ \'ਤੇ ਐਪ ਸੁਝਾਅ ਹਾਸਲ ਕਰੋ"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਆਪਣੀਆਂ ਸਭ ਤੋਂ ਵੱਧ ਵਰਤੀਆਂ ਗਈਆਂ ਐਪਾਂ ਤੱਕ ਆਸਾਨੀ ਨਾਲ ਪਹੁੰਚ ਕਰੋ। ਸੁਝਾਅ ਤੁਹਾਡੇ ਨਿਯਮਬੱਧ ਕੰਮਾਂ ਦੇ ਆਧਾਰ \'ਤੇ ਬਦਲਣਗੇ। ਹੇਠਲੀ ਕਤਾਰ ਵਾਲੀਆਂ ਐਪਾਂ ਤੁਹਾਡੀ ਹੋਮ ਸਕ੍ਰੀਨ ਵੱਲ ਉੱਪਰ ਆ ਜਾਣਗੀਆਂ।"</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਆਪਣੀਆਂ ਸਭ ਤੋਂ ਵੱਧ ਵਰਤੀਆਂ ਗਈਆਂ ਐਪਾਂ ਤੱਕ ਆਸਾਨੀ ਨਾਲ ਪਹੁੰਚ ਕਰੋ। ਸੁਝਾਅ ਤੁਹਾਡੇ ਨਿਯਮਬੱਧ ਕੰਮਾਂ ਦੇ ਆਧਾਰ \'ਤੇ ਬਦਲਣਗੇ। ਮਨਪਸੰਦ ਕਤਾਰ ਵਿਚਲੀਆਂ ਐਪਾਂ ਤੁਹਾਡੀ ਹੋਮ ਸਕ੍ਰੀਨ ਉੱਪਰ ਆ ਜਾਣਗੀਆਂ।"</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਆਪਣੀਆਂ ਸਭ ਤੋਂ ਵੱਧ ਵਰਤੀਆਂ ਗਈਆਂ ਐਪਾਂ ਤੱਕ ਆਸਾਨੀ ਨਾਲ ਪਹੁੰਚ ਕਰੋ। ਸੁਝਾਅ ਤੁਹਾਡੇ ਨਿਯਮਬੱਧ ਕੰਮਾਂ ਦੇ ਆਧਾਰ \'ਤੇ ਬਦਲਣਗੇ। ਹੇਠਲੀ ਕਤਾਰ ਵਾਲੀਆਂ ਐਪਾਂ ਇੱਕ ਨਵੇਂ ਫੋਲਡਰ ਵਿੱਚ ਚਲੀਆਂ ਜਾਣਗੀਆਂ।"</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"ਐਪ ਸੁਝਾਅ ਪ੍ਰਾਪਤ ਕਰੋ"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"ਨਹੀਂ ਧੰਨਵਾਦ"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"ਸੈਟਿੰਗਾਂ"</string>
diff --git a/quickstep/res/values-pl/strings.xml b/quickstep/res/values-pl/strings.xml
index 077982a..3c73de4 100644
--- a/quickstep/res/values-pl/strings.xml
+++ b/quickstep/res/values-pl/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Otrzymuj sugestie aplikacji w wierszu ulubionych na ekranie głównym"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Łatwo uruchamiaj najczęściej używane aplikacje z ekranu głównego. Sugestie będą zmieniać się w zależności od Twoich nawyków. Aplikacje z dolnych wierszy będą przesuwane w górę, do ekranu głównego."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Zyskaj łatwy dostęp do najczęściej używanych aplikacji na ekranie głównym. Sugestie będą się zmieniać na podstawie Twojej rutyny. Aplikacje z wiersza ulubionych zostaną przeniesione na ekran główny."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Korzystaj z najczęściej używanych aplikacji na ekranie głównym w łatwy sposób. Sugestie będą się zmieniać na podstawie Twojej rutyny. Aplikacje z dolnych wierszy będą się przenosić do nowego folderu."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Otrzymuj sugestie aplikacji"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Nie, dziękuję"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Ustawienia"</string>
diff --git a/quickstep/res/values-pt-rPT/strings.xml b/quickstep/res/values-pt-rPT/strings.xml
index 124b33b..5e01dc1 100644
--- a/quickstep/res/values-pt-rPT/strings.xml
+++ b/quickstep/res/values-pt-rPT/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Receba sugestões de apps na fila dos favoritos do ecrã principal"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Aceda facilmente às suas apps mais utilizadas, diretamente no ecrã principal. As sugestões mudam em função das suas rotinas. As apps na última fila passam para o ecrã principal."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Aceda facilmente às suas apps mais utilizadas no ecrã principal. As sugestões mudam em função das suas rotinas. As apps na fila dos favoritos passam para o ecrã principal."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Aceda facilmente às suas apps mais utilizadas, diretamente no ecrã principal. As sugestões mudam em função das suas rotinas. As apps na última fila passam para uma nova pasta."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Obter sugestões de apps"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Não, obrigado"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Definições"</string>
diff --git a/quickstep/res/values-pt/strings.xml b/quickstep/res/values-pt/strings.xml
index 199c2f4..fad2170 100644
--- a/quickstep/res/values-pt/strings.xml
+++ b/quickstep/res/values-pt/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Receba sugestões de apps na linha \"Favoritos\" da tela inicial"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Acesse diretamente na tela inicial os apps que você mais usa. As sugestões mudam de acordo com sua rotina, e os apps na linha inferior são movidos para a tela inicial."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Acesse os apps mais usados na tela inicial de forma simples. As sugestões mudam de acordo com sua rotina, e os apps na linha \"Favoritos\" são movidos para a tela inicial."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Acesse diretamente na tela inicial os apps que você mais usa. As sugestões mudam de acordo com sua rotina, e os apps na linha inferior são movidos para uma nova pasta."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Receber sugestões de apps"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Não"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Configurações"</string>
diff --git a/quickstep/res/values-ro/strings.xml b/quickstep/res/values-ro/strings.xml
index ba4badf..8b8c187 100644
--- a/quickstep/res/values-ro/strings.xml
+++ b/quickstep/res/values-ro/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Primește sugestii de aplicații în rândul de preferințe al ecranului de pornire"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Accesează cu ușurință cele mai folosite aplicații direct din ecranul de pornire. Sugestiile se vor modifica în funcție de rutine. Aplicațiile din rândul de jos se vor muta în sus pe ecranul de pornire."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Accesează cu ușurință cele mai folosite aplicații direct din ecranul de pornire. Sugestiile se vor schimba în funcție de rutina ta. Aplicațiile din rândul de preferințe se vor muta în ecranul de pornire."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Accesează cu ușurință cele mai folosite aplicații, direct din ecranul de pornire. Sugestiile se vor modifica în funcție de rutine. Aplicațiile din rândul de jos se vor muta într-un dosar nou."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Primește sugestii de aplicații"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Nu, mulțumesc"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Setări"</string>
diff --git a/quickstep/res/values-ru/strings.xml b/quickstep/res/values-ru/strings.xml
index 44b72cf..e078ba0 100644
--- a/quickstep/res/values-ru/strings.xml
+++ b/quickstep/res/values-ru/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Рекомендуемые приложения будут появляться в разделе избранных на главном экране"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Приложения, которыми вы часто пользуетесь, будут доступны прямо на главном экране. Их список может меняться с учетом ваших предпочтений. Приложения из нижнего ряда будут перемещены выше на главном экране."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Включите функцию для быстрого доступа к часто используемым приложениям на главном экране. Список меняется с учетом ваших действий. Приложения из раздела избранных будут перемещены на главный экран."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Приложения, которыми вы часто пользуетесь, будут доступны прямо на главном экране. Их список может меняться с учетом ваших предпочтений. Приложения из нижнего ряда будут перемещены в новую папку."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Показывать рекомендации"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Отмена"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Настройки"</string>
diff --git a/quickstep/res/values-si/strings.xml b/quickstep/res/values-si/strings.xml
index 31749cc..f890548 100644
--- a/quickstep/res/values-si/strings.xml
+++ b/quickstep/res/values-si/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"ඔබේ මුල් තිරයේ ප්‍රියතම පේළියේ යෙදුම් යෝජනා ලබා ගන්න"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"ඔබගේ වැඩිපුරම භාවිත කරන යෙදුම්වලට මුල් තිරයේ සිටම පහසුවෙන් ප්‍රවේශ වන්න. ඔබගේ දිනචරියා මත පදනම්ව යෝජනා වෙනස් වනු ඇත. පහළ පේළියේ යෙදුම් ඔබගේ මුල් තිරය දක්වා ගෙන යනු ඇත."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"ඔබගේ වැඩිපුරම භාවිත කරන යෙදුම්වලට මුල් තිරයේ සිටම පහසුවෙන් ප්‍රවේශ වන්න. ඔබගේ දිනචරියා මත පදනම්ව යෝජනා වෙනස් වනු ඇත. ප්‍රියතම තුළ යෙදුම් ඔබේ මුල් තිරය වෙත ගෙන යනු ඇත."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"ඔබගේ වැඩිපුරම භාවිත කරන යෙදුම්වලට මුල් තිරයේ සිටම පහසුවෙන් ප්‍රවේශ වන්න. ඔබගේ දිනචරියා මත පදනම්ව යෝජනා වෙනස් වනු ඇත. පහළ පේළියේ යෙදුම් නව ෆෝල්ඩරයකට ගෙන යනු ඇත."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"යෙදුම් යෝජනා ලබා ගන්න"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"එපා ස්තුතියි"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"සැකසීම්"</string>
diff --git a/quickstep/res/values-sk/strings.xml b/quickstep/res/values-sk/strings.xml
index 5d51d45..cdf3339 100644
--- a/quickstep/res/values-sk/strings.xml
+++ b/quickstep/res/values-sk/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Nechajte si na ploche na riadku obľúbených zobrazovať návrhy aplikácií"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Získajte jednoduchý prístup k najpoužívanejším aplikáciám priamo na ploche. Návrhy sa budú meniť podľa vašich zvyklostí. Aplikácie v spodnom riadku sa presunú nahor na plochu."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Získajte jednoduchý prístup k najpoužívanejším aplikáciám priamo na ploche. Návrhy sa budú meniť podľa vašich postupov. Aplikácie v riadku s obľúbenými sa presunú na plochu."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Získajte jednoduchý prístup k najpoužívanejším aplikáciám priamo na ploche. Návrhy sa budú meniť podľa vašich zvyklostí. Aplikácie v spodnom riadku sa presunú do nového priečinka."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Zobrazovať návrhy aplikácií"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Nie, ďakujem"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Nastavenia"</string>
diff --git a/quickstep/res/values-sl/strings.xml b/quickstep/res/values-sl/strings.xml
index e196e5d..3e7c5ce 100644
--- a/quickstep/res/values-sl/strings.xml
+++ b/quickstep/res/values-sl/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Prejemajte predloge aplikacij v vrstici s priljubljenimi na začetnem zaslonu"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Preprosto dostopajte do najpogosteje uporabljenih aplikacij kar na začetnem zaslonu. Predlogi se spreminjajo na podlagi dejanj, ki jih pogosto izvajate. Aplikacije iz spodnje vrstice se premaknejo na začetni zaslon."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Preprosto dostopajte do najpogosteje uporabljenih aplikacij kar na začetnem zaslonu. Predlogi se spreminjajo na podlagi dejanj, ki jih pogosto izvajate. Aplikacije v vrstici s priljubljenimi bodo premaknjene na začetni zaslon."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Preprosto dostopajte do najpogosteje uporabljenih aplikacij kar na začetnem zaslonu. Predlogi se spreminjajo na podlagi dejanj, ki jih pogosto izvajate. Aplikacije iz spodnje vrstice se premaknejo v novo mapo."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Prikaži predlagane aplikacije"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Ne, hvala"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Nastavitve"</string>
diff --git a/quickstep/res/values-sq/strings.xml b/quickstep/res/values-sq/strings.xml
index 39771cb..04b7bc1 100644
--- a/quickstep/res/values-sq/strings.xml
+++ b/quickstep/res/values-sq/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Merr aplikacione të sugjeruara në rreshtin e të preferuarave të ekranit tënd bazë"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Qasu me lehtësi në aplikacionet më të përdorura direkt në ekranin bazë. Sugjerimet do të ndryshojnë bazuar në rutinat e tua. Aplikacionet në rreshtin e poshtëm do të zhvendosen lart në ekranin tënd bazë."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Qasu me lehtësi në aplikacionet më të përdorura direkt në ekranin bazë. Sugjerimet do të ndryshojnë bazuar në rutinat e tua. Aplikacionet në rreshtin e të preferuarave do të zhvendosen në ekranin tënd bazë."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Qasu me lehtësi në aplikacionet më të përdorura, direkt në ekranin bazë. Sugjerimet do të ndryshojnë bazuar në rutinat e tua. Aplikacionet në rreshtin e poshtëm do të zhvendosen në një dosje tjetër."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Merr aplikacione të sugjeruara"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Jo, faleminderit"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Cilësimet"</string>
diff --git a/quickstep/res/values-sr/strings.xml b/quickstep/res/values-sr/strings.xml
index 067a7fe..215fde9 100644
--- a/quickstep/res/values-sr/strings.xml
+++ b/quickstep/res/values-sr/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Добијајте предлоге апликација у реду са омиљеним ставкама на почетном екрану"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Лако приступајте апликацијама које најчешће користите директно са почетног екрана. Предлози се мењају на основу употребе. Апликације из доњег реда се премештају нагоре на почетни екран."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Лако приступајте апликацијама које најчешће користите директно са почетног екрана. Предлози се мењају на основу ваших рутина. Апликације из реда са омиљеним ставкама се премештају на почетни екран."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Лако приступајте апликацијама које најчешће користите директно са почетног екрана. Предлози се мењају на основу употребе. Апликације из доњег реда се премештају у нов фолдер."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Приказуј предлоге апликација"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Не, хвала"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Подешавања"</string>
diff --git a/quickstep/res/values-sv/strings.xml b/quickstep/res/values-sv/strings.xml
index 3bb47c3..e7c0226 100644
--- a/quickstep/res/values-sv/strings.xml
+++ b/quickstep/res/values-sv/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Få appförslag på raden Favoriter på startskärmen"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Kom enkelt åt de appar du använder mest, direkt från startskärmen. Förslagen ändras efter dina rutiner. Appar på nedersta raden flyttas upp till startskärmen."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Kom enkelt åt de appar du använder mest, direkt från startskärmen. Förslagen ändras efter dina rutiner. Appar på raden Favoriter flyttas till startskärmen."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Kom enkelt åt de appar du använder mest, direkt från startskärmen. Förslagen ändras efter dina rutiner. Appar på nedersta raden flyttas till en ny mapp."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Få appförslag"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Nej tack"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Inställningar"</string>
diff --git a/quickstep/res/values-sw/strings.xml b/quickstep/res/values-sw/strings.xml
index 16891a8..b1a178e 100644
--- a/quickstep/res/values-sw/strings.xml
+++ b/quickstep/res/values-sw/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Pata mapendekezo ya programu katika safu ya vipendwa ya Skrini yako ya kwanza"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Fikia kwa urahisi programu unazotumia sana moja kwa moja kwenye Skrini ya kwanza. Mapendekezo yatabadilika kulingana na ratiba zako. Programu zilizo kwenye sehemu ya chini zitahamishiwa kwenye Skrini yako ya kwanza."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Fikia kwa urahisi programu unazotumia sana moja kwa moja kwenye Skrini ya kwanza. Mapendekezo yatabadilika kulingana na utumiaji wako. Programu zilizo katika safu ya vipendwa zitahamishiwa kwenye Skrini yako ya kwanza."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Fikia kwa urahisi programu unazotumia zaidi, moja kwa moja kwenye Skrini ya kwanza. Mapendekezo yatabadilika kulingana na ratiba zako. Programu zilizo kwenye safu ya chini zitahamishiwa kwenye folda mpya."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Pata mapendekezo ya programu"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Hapana"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Mipangilio"</string>
diff --git a/quickstep/res/values-sw600dp/dimens.xml b/quickstep/res/values-sw600dp/dimens.xml
index 7494683..cfbbf8d 100644
--- a/quickstep/res/values-sw600dp/dimens.xml
+++ b/quickstep/res/values-sw600dp/dimens.xml
@@ -14,16 +14,23 @@
  * limitations under the License.
 */
 -->
+<!-- Applies to small tablet screens -->
 <resources>
     <dimen name="navigation_key_padding">25dp</dimen>
 
-    <!--  Task View  -->
+    <!--  Overview Task Views  -->
+    <!--  A touch target for icons, sometimes slightly larger than the icons themselves  -->
     <dimen name="task_thumbnail_icon_size">48dp</dimen>
+    <!--  The icon size for the focused task, placed in center of touch target  -->
     <dimen name="task_thumbnail_icon_drawable_size">44dp</dimen>
+    <!--  The space under the focused task icon  -->
     <dimen name="overview_task_margin">12dp</dimen>
+    <!--  The icon size of all non-focused task icons, placed in center of touch target  -->
     <dimen name="task_thumbnail_icon_drawable_size_grid">44dp</dimen>
-    <dimen name="overview_task_margin_grid">12dp</dimen>
+    <!--  The space between grid rows (when there's 2 rows of thumbnails)  -->
     <dimen name="overview_grid_row_spacing">28dp</dimen>
+    <!--  The horizontal space between tasks  -->
     <dimen name="overview_page_spacing">36dp</dimen>
+    <!--  The space to the left and to the right of the "Clear all" button  -->
     <dimen name="overview_grid_side_margin">64dp</dimen>
 </resources>
diff --git a/quickstep/res/values-sw720dp/dimens.xml b/quickstep/res/values-sw720dp/dimens.xml
index ceaa8f8..284ce11 100644
--- a/quickstep/res/values-sw720dp/dimens.xml
+++ b/quickstep/res/values-sw720dp/dimens.xml
@@ -14,14 +14,23 @@
  * limitations under the License.
 */
 -->
+<!-- Applies to large tablet screens -->
 <resources>
-    <!--  Task View  -->
+    <!--  Overview Task Views  -->
+    <!--  The primary task thumbnail uses up to this much of the total screen height/width  -->
+    <item name="overview_max_scale" format="float" type="dimen">0.7</item>
+    <!--  A touch target for icons, sometimes slightly larger than the icons themselves  -->
     <dimen name="task_thumbnail_icon_size">48dp</dimen>
+    <!--  The icon size for the focused task, placed in center of touch target  -->
     <dimen name="task_thumbnail_icon_drawable_size">44dp</dimen>
+    <!--  The space under the focused task icon  -->
     <dimen name="overview_task_margin">16dp</dimen>
+    <!--  The icon size of all non-focused task icons, placed in center of touch target  -->
     <dimen name="task_thumbnail_icon_drawable_size_grid">44dp</dimen>
-    <dimen name="overview_task_margin_grid">16dp</dimen>
+    <!--  The space between grid rows (when there's 2 rows of thumbnails)  -->
     <dimen name="overview_grid_row_spacing">36dp</dimen>
+    <!--  The horizontal space between tasks  -->
     <dimen name="overview_page_spacing">44dp</dimen>
+    <!--  The space to the left and to the right of the "Clear all" button  -->
     <dimen name="overview_grid_side_margin">64dp</dimen>
 </resources>
diff --git a/quickstep/res/values-ta/strings.xml b/quickstep/res/values-ta/strings.xml
index 5c73734..431ae3c 100644
--- a/quickstep/res/values-ta/strings.xml
+++ b/quickstep/res/values-ta/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"உங்கள் முகப்புத் திரையின் \'பிடித்தவை\' வரிசையில் ஆப்ஸ் பரிந்துரைகளைப் பெறலாம்"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"அதிகமாகப் பயன்படுத்திய ஆப்ஸை முகப்புத் திரையிலேயே அணுகலாம். உங்கள் வழக்கங்களின் அடிப்படையில் பரிந்துரைகள் மாறும். கடைசி வரிசையிலுள்ள ஆப்ஸ் உங்கள் முகப்புத் திரைக்கு நகர்த்தப்படும்."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"அதிகமாகப் பயன்படுத்திய ஆப்ஸை முகப்புத் திரையிலேயே எளிதாக அணுகலாம். உங்கள் வழக்கங்களின் அடிப்படையில் பரிந்துரைகள் மாறும். பிடித்தவை வரிசையில் உள்ள ஆப்ஸ் உங்கள் முகப்புத் திரைக்கு நகர்த்தப்படும்."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"அதிகமாகப் பயன்படுத்திய ஆப்ஸை முகப்புத் திரையிலேயே அணுகலாம். உங்கள் வழக்கங்களின் அடிப்படையில் பரிந்துரைகள் மாறும். கடைசி வரிசையிலுள்ள ஆப்ஸ் புதிய ஃபோல்டருக்கு நகர்த்தப்படும்."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"ஆப்ஸ் பரிந்துரைகளைப் பெறுக"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"வேண்டாம்"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"அமைப்புகள்"</string>
diff --git a/quickstep/res/values-te/strings.xml b/quickstep/res/values-te/strings.xml
index 332e95d..7b6dedf 100644
--- a/quickstep/res/values-te/strings.xml
+++ b/quickstep/res/values-te/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"మీ హోమ్ స్క్రీన్‌లోని ఇష్టమైన వాటి వరుసలో యాప్ సూచ‌న‌లు పొందండి"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"మీరు ఎక్కువగా ఉపయోగించే యాప్‌లను నేరుగా మొదటి స్క్రీన్‌లోనే సులభంగా యాక్సెస్ చేయండి. మీ రోజువారీ యాక్టివిటీలను బట్టి సూచనలు మారతాయి. దిగువ వరుసలోని యాప్‌లు మీ మొదటి స్క్రీన్ పైకి చేరుకుంటాయి."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"మీరు ఎక్కువగా ఉపయోగించే యాప్‌లను నేరుగా మొదటి స్క్రీన్‌లోనే సులభంగా యాక్సెస్ చేయండి. మీ రోజువారీ యాక్టివిటీలను బట్టి సూచనలు మారతాయి. ఇష్టమైన వాటి వరుసలోని యాప్‌లు మీ మొదటి స్క్రీన్‌కు చేరుకుంటాయి."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"మీరు ఎక్కువగా ఉపయోగించే యాప్‌లను నేరుగా మొదటి స్క్రీన్‌లోనే సులభంగా యాక్సెస్ చేయండి. మీ రోజువారీ యాక్టివిటీలను బట్టి సూచనలు మారతాయి. దిగువ వరుసలోని యాప్‌లు కొత్త ఫోల్డర్‌కు తరలించబడతాయి."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"యాప్ సూచ‌న‌లను పొందండి"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"వద్దు"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"సెట్టింగ్‌లు"</string>
diff --git a/quickstep/res/values-th/strings.xml b/quickstep/res/values-th/strings.xml
index 59a84ff..4d5794b 100644
--- a/quickstep/res/values-th/strings.xml
+++ b/quickstep/res/values-th/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"รับคำแนะนำเกี่ยวกับแอปในแถวรายการโปรดของหน้าจอหลัก"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"เข้าถึงแอปที่คุณใช้มากที่สุดได้อย่างง่ายดายจากหน้าจอหลัก การแนะนำจะเปลี่ยนไปตามแอปที่ใช้งานเป็นประจำ แอปในแถวล่างจะย้ายขึ้นมาอยู่ในหน้าจอหลัก"</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"เข้าถึงแอปที่คุณใช้มากที่สุดได้อย่างง่ายดายจากหน้าจอหลัก การแนะนำจะเปลี่ยนไปตามแอปที่ใช้งานเป็นประจำ แอปในแถวรายการโปรดจะย้ายไปอยู่ในหน้าจอหลัก"</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"เข้าถึงแอปที่คุณใช้มากที่สุดได้อย่างง่ายดายจากหน้าจอหลัก การแนะนำจะเปลี่ยนไปตามแอปที่ใช้งานเป็นประจำ แอปในแถวล่างจะย้ายไปอยู่ในโฟลเดอร์ใหม่"</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"ดูแอปแนะนำ"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"ไม่เป็นไร"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"การตั้งค่า"</string>
diff --git a/quickstep/res/values-tl/strings.xml b/quickstep/res/values-tl/strings.xml
index b22d85d..6699ed8 100644
--- a/quickstep/res/values-tl/strings.xml
+++ b/quickstep/res/values-tl/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Makakuha ng mga iminumungkahing app sa row ng mga paborito ng iyong Home screen"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Madaling ma-access ang mga pinakaginagamit mong app nang direkta sa Home screen. Magbabago ang mga suhestyon batay sa iyong mga routine. Mapupunta sa iyong Home screen ang mga app na nasa ibabang row."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Madaling ma-access ang mga pinakaginagamit mong app nang direkta sa Home screen. Magbabago ang mga suhestyon batay sa iyong mga routine. Mapupunta sa iyong Home screen ang mga app sa row ng mga paborito."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Madaling ma-access ang mga pinakaginagamit mong app, direkta sa Home screen. Magbabago ang mga suhestyon batay sa iyong mga routine. Mapupunta sa isang bagong folder ang mga app na nasa ibabang row."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Kumuha ng mga suhestiyon sa app"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Huwag na lang"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Mga Setting"</string>
diff --git a/quickstep/res/values-tr/strings.xml b/quickstep/res/values-tr/strings.xml
index b3054f7..24e25ff 100644
--- a/quickstep/res/values-tr/strings.xml
+++ b/quickstep/res/values-tr/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Ana ekranınızın favoriler satırında uygulama önerileri alın"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"En çok kullanılan uygulamalarınıza Ana ekranda kolayca erişin. Öneriler, rutinlerinize dayalı olarak değişir. Alt satırdaki uygulamalar, yukarı taşınarak Ana ekranınıza alınır."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"En çok kullanılan uygulamalarınıza Ana ekrandan kolayca erişin. Öneriler rutinlerinize dayalı olarak değişir. Favoriler satırındaki uygulamalar Ana ekranınıza taşınır."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"En çok kullanılan uygulamalarınıza Ana ekranda kolayca erişin. Öneriler, rutinlerinize dayalı olarak değişir. Alt satırdaki uygulamalar yeni bir klasöre taşınır."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Uygulama önerileri al"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Hayır, teşekkürler"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Ayarlar"</string>
diff --git a/quickstep/res/values-uk/strings.xml b/quickstep/res/values-uk/strings.xml
index 490ac2d..8dd65e2 100644
--- a/quickstep/res/values-uk/strings.xml
+++ b/quickstep/res/values-uk/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Рекомендовані додатки з\'являтимуться в рядку \"Вибране\" на головному екрані"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"З легкістю відкривайте на головному екрані ті додатки, які використовуєте найчастіше. Рекомендації змінюватимуться залежно від ваших дій. Додатки в нижньому рядку перемістяться на головний екран."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"З легкістю відкривайте найпотрібніші додатки просто з головного екрана. Рекомендації змінюватимуться залежно від ваших дій. Додатки з рядка \"Вибране\" буде переміщено на головний екран."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"З легкістю відкривайте найвикористовуваніші додатки просто з головного екрана. Рекомендації змінюватимуться залежно від ваших дій. Додатки в нижньому рядку буде переміщено в нову папку."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Показувати рекомендації"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Не потрібно"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Налаштування"</string>
diff --git a/quickstep/res/values-ur/strings.xml b/quickstep/res/values-ur/strings.xml
index 8f2da65..2d9a1e3 100644
--- a/quickstep/res/values-ur/strings.xml
+++ b/quickstep/res/values-ur/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"اپنی ہوم اسکرین کی پسندیدہ قطار پر ایپ کی تجاویز حاصل کریں"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"ہوم اسکرین پر آسانی سے اپنی سب سے زیادہ مستعمل ایپس تک رسائی حاصل کریں۔ آپ کی روٹینز کی بنیاد پر تجاویز تبدیل ہوں گی۔ نچلی قطار میں موجود ایپس آپ کی ہوم اسکرین کے اوپر منتقل ہوں گی۔"</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"ہوم اسکرین پر آسانی سے اپنی سب سے زیادہ مستعمل ایپس تک رسائی حاصل کریں۔ آپ کی روٹینز کی بنیاد پر تجاویز تبدیل ہوں گی۔ پسندیدہ میں موجود ایپس آپ کی ہوم اسکرین کے اوپر منتقل ہوں گی۔"</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"ہوم اسکرین پر، آسانی سے اپنی سب سے زیادہ مستعمل ایپس تک رسائی حاصل کریں۔ آپ کی روٹینز کی بنیاد پر تجاویز تبدیل ہوں گی۔ نچلی قطار میں موجود ایپس نئے فولڈر میں منتقل ہوں گی۔"</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"ایپس کی تجاویز حاصل کریں"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"نہیں شکریہ"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"ترتیبات"</string>
diff --git a/quickstep/res/values-uz/strings.xml b/quickstep/res/values-uz/strings.xml
index 9c47cef..9446325 100644
--- a/quickstep/res/values-uz/strings.xml
+++ b/quickstep/res/values-uz/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Tavsiya etiladigan ilovalar bosh ekranning saralanganlar ruknida chiqadi"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Faol ishlatiladigan ilovalarga bosh ekrandan osongina kira olasiz. Tavsiyalar oxirgi faoliyatingiz asosida almashib boradi. Pastki qatordagi ilovalar bosh ekranga chiqadi."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Faol ishlatiladigan ilovalarga bosh ekrandan osongina kira olasiz. Tavsiyalar oxirgi faoliyatingiz asosida almashib boradi. Saralanganlar qatoridagi ilovalar bosh ekranga chiqadi."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Faol ishlatiladigan ilovalarga bosh ekrandan osongina kira olasiz. Tavsiyalar oxirgi faoliyatingiz asosida almashib boradi. Pastki qatordagi ilovalar yangi jildga chiqadi."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Tavsiyalarni chiqarish"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Kerak emas"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Sozlamalar"</string>
diff --git a/quickstep/res/values-vi/strings.xml b/quickstep/res/values-vi/strings.xml
index 2e5c3e7..55027ae 100644
--- a/quickstep/res/values-vi/strings.xml
+++ b/quickstep/res/values-vi/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Nhận các ứng dụng đề xuất trên hàng mục ưa thích của Màn hình chính"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Bạn có thể dễ dàng truy cập những ứng dụng mà mình dùng thường xuyên nhất ngay trên màn hình chính. Các ứng dụng đề xuất sẽ thay đổi dựa trên thói quen của bạn. Các ứng dụng ở hàng dưới cùng sẽ chuyển lên phía trên của Màn hình chính."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Bạn có thể dễ dàng mở những ứng dụng mà mình dùng thường xuyên nhất ngay trên màn hình chính. Các ứng dụng đề xuất sẽ thay đổi dựa trên thói quen của bạn. Các ứng dụng ở hàng mục ưa thích sẽ chuyển sang Màn hình chính."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Bạn có thể dễ dàng truy cập những ứng dụng mà mình dùng thường xuyên nhất ngay trên màn hình chính. Các ứng dụng đề xuất sẽ thay đổi dựa trên thói quen của bạn. Các ứng dụng ở hàng dưới cùng sẽ chuyển đến một thư mục mới."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Nhận ứng dụng đề xuất"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Không, cảm ơn"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Cài đặt"</string>
diff --git a/quickstep/res/values-zh-rCN/strings.xml b/quickstep/res/values-zh-rCN/strings.xml
index 0b5193f..debf23a 100644
--- a/quickstep/res/values-zh-rCN/strings.xml
+++ b/quickstep/res/values-zh-rCN/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"在主屏幕的收藏行获取应用建议"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"直接在主屏幕上轻松访问您最常用的应用。系统会根据您的日常安排提供不同的建议。最下面一排中的应用会向上移到主屏幕中。"</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"直接在主屏幕上轻松访问您最常用的应用。建议会因您的日常安排而变化,收藏行中的应用将移到主屏幕上。"</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"直接在主屏幕上轻松访问您最常用的应用。系统会根据您的日常安排提供不同的建议。最下面一排中的应用会移到新文件夹中。"</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"获取应用建议"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"不用了"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"设置"</string>
diff --git a/quickstep/res/values-zh-rHK/strings.xml b/quickstep/res/values-zh-rHK/strings.xml
index b700da0..c7a44e7 100644
--- a/quickstep/res/values-zh-rHK/strings.xml
+++ b/quickstep/res/values-zh-rHK/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"在主畫面「我的最愛」列取得應用程式建議"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"在主畫面輕鬆存取常用的應用程式。系統會根據您的日常安排更改建議,並將底部的應用程式移到主畫面。"</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"在主畫面輕鬆存取最常用的應用程式。系統會根據您的日常安排變更建議,「我的最愛」列中的應用程式會移至主畫面。"</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"在主畫面輕鬆存取最常用的應用程式。系統會根據您的日常安排變更建議,並將底列的應用程式移至新資料夾。"</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"取得應用程式建議"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"不用了,謝謝"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"設定"</string>
diff --git a/quickstep/res/values-zh-rTW/strings.xml b/quickstep/res/values-zh-rTW/strings.xml
index e7b74a4..15d5de5 100644
--- a/quickstep/res/values-zh-rTW/strings.xml
+++ b/quickstep/res/values-zh-rTW/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"在主畫面的收藏列取得應用程式建議"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"你可以輕鬆地在主畫面上找到自己常用的應用程式。應用程式建議會依據你的日常使用習慣而有所不同。系統會將底部列出的應用程式上移到主畫面。"</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"你可以輕鬆地在主畫面上找到自己常用的應用程式。系統會根據你的日常使用習慣提供不同的應用程式建議,並在主畫面顯示收藏列中的應用程式。"</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"你可以輕鬆地在主畫面上找到自己常用的應用程式。應用程式建議會根據日常安排有所不同。系統會將底部列出的應用程式移到新的資料夾。"</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"取得應用程式建議"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"不用了,謝謝"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"設定"</string>
diff --git a/quickstep/res/values-zu/strings.xml b/quickstep/res/values-zu/strings.xml
index c69f63d..3db5836 100644
--- a/quickstep/res/values-zu/strings.xml
+++ b/quickstep/res/values-zu/strings.xml
@@ -35,7 +35,6 @@
     <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Thola iziphakamiso zohlelo lokusebenza kumugqa wezintandokazi Zesikrini sakho sasekhaya"</string>
     <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Finyelela kalula izinhlelo zakho zokusebenza ezisetshenziswa kakhulu khona kusikrini sasekhaya. Iziphakamiso zizoshintsha ngokususelwe kwimijikelezo yakho. Izinhlelo zokusebenza ezisemgqeni ongezansi zizoya phezulu kusikrini sakho sasekhaya."</string>
     <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Finyelela kalula izinhlelo zakho zokusebenza ezisetshenziswa kakhulu khona kusikrini sasekhaya. Iziphakamiso zizoshintsha ngokususelwe kwimijikelezo yakho. Izintandokazi zomugqa wezinhlelo zokusebenza zizoya Kusikrini sakho sasekhaya."</string>
-    <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"Finyelela kalula izinhlelo zakho zokusebenza ezisetshenziswa njalo, kusikrini sasekhaya. Iziphakamiso zizoshintsha ngokususelwe kwimijikelezo yakho. Izinhlelo zokusebenza ezisemgqeni ongezansi zizoya phezulu kufolda entsha."</string>
     <string name="hotseat_edu_accept" msgid="1611544083278999837">"Thola iziphakamiso zohlelo lokusebenza"</string>
     <string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Cha ngiyabonga"</string>
     <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Amasethingi"</string>
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index 3b4a28b..198a676 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -43,4 +43,9 @@
     <!-- Accessibility actions -->
     <item type="id" name="action_move_to_top_or_left" />
     <item type="id" name="action_move_to_bottom_or_right" />
+
+    <!-- The max scale for the wallpaper when it's zoomed in -->
+    <item name="config_wallpaperMaxScale" format="float" type="dimen">
+        @*android:dimen/config_wallpaperMaxScale
+    </item>
 </resources>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 3072a3e..c85e71c 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -31,13 +31,19 @@
 
     <dimen name="overview_minimum_next_prev_size">50dp</dimen>
 
-    <!--  Task View  -->
+    <!--  Overview Task Views  -->
+    <!--  The primary task thumbnail uses up to this much of the total screen height/width  -->
+    <item name="overview_max_scale" format="float" type="dimen">0.7</item>
+    <!--  A touch target for icons, sometimes slightly larger than the icons themselves  -->
     <dimen name="task_thumbnail_icon_size">48dp</dimen>
+    <!--  The icon size for the focused task, placed in center of touch target  -->
     <dimen name="task_thumbnail_icon_drawable_size">44dp</dimen>
+    <!--  The space under the focused task icon  -->
     <dimen name="overview_task_margin">16dp</dimen>
+    <!--  The horizontal space between tasks  -->
     <dimen name="overview_page_spacing">16dp</dimen>
 
-    <item name="overview_max_scale" format="float" type="dimen">0.7</item>
+    <dimen name="task_icon_cache_default_icon_size">72dp</dimen>
     <item name="overview_modal_max_scale" format="float" type="dimen">1.1</item>
 
     <!-- Overrideable in overlay that provides the Overview Actions. -->
@@ -66,7 +72,6 @@
     <dimen name="quickstep_fling_threshold_speed">0.5dp</dimen>
 
     <!-- Launcher app transition -->
-    <item name="content_scale" format="float" type="dimen">0.97</item>
     <dimen name="closing_window_trans_y">115dp</dimen>
 
     <dimen name="quick_switch_scaling_scroll_threshold">100dp</dimen>
@@ -239,7 +244,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>
@@ -250,14 +255,15 @@
     <dimen name="taskbar_icon_touch_size">48dp</dimen>
     <dimen name="taskbar_icon_drag_icon_size">54dp</dimen>
     <dimen name="taskbar_folder_margin">16dp</dimen>
-    <dimen name="taskbar_nav_buttons_spacing">16dp</dimen>
+    <dimen name="taskbar_contextual_button_padding">16dp</dimen>
     <dimen name="taskbar_contextual_padding_top">8dp</dimen>
     <dimen name="taskbar_nav_buttons_size">44dp</dimen>
-    <dimen name="taskbar_contextual_button_margin">40dp</dimen>
-    <dimen name="taskbar_hotseat_nav_spacing">42dp</dimen>
+    <dimen name="taskbar_contextual_button_margin">47dp</dimen>
+    <dimen name="taskbar_hotseat_nav_spacing">24dp</dimen>
     <dimen name="taskbar_contextual_buttons_size">35dp</dimen>
     <dimen name="taskbar_stashed_size">24dp</dimen>
     <dimen name="taskbar_stashed_handle_width">220dp</dimen>
+    <dimen name="taskbar_stashed_small_screen">108dp</dimen>
     <dimen name="taskbar_unstash_input_area">316dp</dimen>
     <dimen name="taskbar_stashed_handle_height">4dp</dimen>
     <dimen name="taskbar_edu_wave_anim_trans_y">25dp</dimen>
@@ -268,4 +274,13 @@
     <dimen name="taskbar_back_button_left_margin_kids">48dp</dimen>
     <dimen name="taskbar_home_button_left_margin_kids">48dp</dimen>
     <dimen name="taskbar_icon_size_kids">32dp</dimen>
+
+    <!--  Taskbar 3 button spacing  -->
+    <dimen name="taskbar_button_space_inbetween">24dp</dimen>
+    <dimen name="taskbar_button_space_inbetween_phone">40dp</dimen>
+    <dimen name="taskbar_button_margin_5_5">26dp</dimen>
+    <dimen name="taskbar_button_margin_6_5">75dp</dimen>
+    <dimen name="taskbar_button_margin_4_5">47dp</dimen>
+    <dimen name="taskbar_button_margin_4_4">47dp</dimen>
+    <dimen name="taskbar_button_margin_default">47dp</dimen>
 </resources>
diff --git a/quickstep/res/values/override.xml b/quickstep/res/values/override.xml
index 705ec9d..4f472f0 100644
--- a/quickstep/res/values/override.xml
+++ b/quickstep/res/values/override.xml
@@ -25,4 +25,6 @@
 
   <string name="model_delegate_class" translatable="false">com.android.launcher3.model.QuickstepModelDelegate</string>
 
+  <string name="secondary_display_predictions_class" translatable="false">com.android.launcher3.secondarydisplay.SecondaryDisplayPredictionsImpl</string>
+
 </resources>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 81b0dd2..c0d52a4 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -68,7 +68,6 @@
 
     <string name="hotseat_edu_message_migrate">Easily access your most-used apps right on the Home screen. Suggestions will change based on your routines. Apps on the bottom row will move up to your Home screen. </string>
     <string name="hotseat_edu_message_migrate_landscape">Easily access your most-used apps right on the Home screen. Suggestions will change based on your routines. Apps in favorites row will move to your Home screen. </string>
-    <string name="hotseat_edu_message_migrate_alt">Easily access your most-used apps, right on the Home screen. Suggestions will change based on your routines. Apps on the bottom row will move to a new folder.</string>
 
     <!-- Button text to opt in for fully predicted hotseat -->
     <string name="hotseat_edu_accept">Get app suggestions</string>
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
deleted file mode 100644
index 2239102..0000000
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ /dev/null
@@ -1,641 +0,0 @@
-/*
- * Copyright (C) 2019 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 static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
-import static com.android.launcher3.AbstractFloatingView.TYPE_HIDE_BACK_BUTTON;
-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.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;
-import static com.android.launcher3.popup.QuickstepSystemShortcut.getSplitSelectShortcutByPosition;
-import static com.android.launcher3.taskbar.LauncherTaskbarUIController.ALL_APPS_PAGE_PROGRESS_INDEX;
-import static com.android.launcher3.taskbar.LauncherTaskbarUIController.MINUS_ONE_PAGE_PROGRESS_INDEX;
-import static com.android.launcher3.taskbar.LauncherTaskbarUIController.WIDGETS_PAGE_PROGRESS_INDEX;
-import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
-import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
-import static com.android.launcher3.util.DisplayController.NavigationMode.TWO_BUTTONS;
-import static com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
-
-import android.animation.AnimatorSet;
-import android.animation.ValueAnimator;
-import android.app.ActivityManager;
-import android.app.ActivityOptions;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentSender;
-import android.hardware.SensorManager;
-import android.hardware.devicestate.DeviceStateManager;
-import android.os.Bundle;
-import android.os.CancellationSignal;
-import android.os.IBinder;
-import android.view.Display;
-import android.view.View;
-import android.window.SplashScreen;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.model.WellbeingModel;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.popup.SystemShortcut;
-import com.android.launcher3.proxy.ProxyActivityStarter;
-import com.android.launcher3.proxy.StartActivityParams;
-import com.android.launcher3.statehandlers.BackButtonAlphaHandler;
-import com.android.launcher3.statehandlers.DepthController;
-import com.android.launcher3.statemanager.StateManager.StateHandler;
-import com.android.launcher3.taskbar.LauncherTaskbarUIController;
-import com.android.launcher3.taskbar.TaskbarManager;
-import com.android.launcher3.uioverrides.RecentsViewStateController;
-import com.android.launcher3.util.ActivityOptionsWrapper;
-import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.DisplayController.NavigationMode;
-import com.android.launcher3.util.IntSet;
-import com.android.launcher3.util.ObjectWrapper;
-import com.android.launcher3.util.RunnableList;
-import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
-import com.android.launcher3.util.UiThreadHelper;
-import com.android.quickstep.OverviewCommandHelper;
-import com.android.quickstep.RecentsModel;
-import com.android.quickstep.SystemUiProxy;
-import com.android.quickstep.TaskUtils;
-import com.android.quickstep.TouchInteractionService.TISBinder;
-import com.android.quickstep.util.LauncherUnfoldAnimationController;
-import com.android.quickstep.util.ProxyScreenStatusProvider;
-import com.android.quickstep.util.RemoteAnimationProvider;
-import com.android.quickstep.util.RemoteFadeOutAnimationListener;
-import com.android.quickstep.util.SplitSelectStateController;
-import com.android.quickstep.util.TISBindHelper;
-import com.android.quickstep.views.OverviewActionsView;
-import com.android.quickstep.views.RecentsView;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.ActivityOptionsCompat;
-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.UnfoldTransitionConfig;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Stream;
-
-/**
- * Extension of Launcher activity to provide quickstep specific functionality
- */
-public abstract class BaseQuickstepLauncher extends Launcher {
-
-    private DepthController mDepthController = new DepthController(this);
-    private QuickstepTransitionManager mAppTransitionManager;
-
-    /**
-     * Reusable command for applying the back button alpha on the background thread.
-     */
-    public static final UiThreadHelper.AsyncCommand SET_BACK_BUTTON_ALPHA =
-            (context, arg1, arg2) -> SystemUiProxy.INSTANCE.get(context).setNavBarButtonAlpha(
-                    Float.intBitsToFloat(arg1), arg2 != 0);
-
-    private OverviewActionsView mActionsView;
-
-    private TISBindHelper mTISBindHelper;
-    private @Nullable TaskbarManager mTaskbarManager;
-    private @Nullable OverviewCommandHelper mOverviewCommandHelper;
-    private @Nullable LauncherTaskbarUIController mTaskbarUIController;
-
-    // Will be updated when dragging from taskbar.
-    private @Nullable DragOptions mNextWorkspaceDragOptions = null;
-
-    private @Nullable UnfoldTransitionProgressProvider mUnfoldTransitionProgressProvider;
-    private @Nullable LauncherUnfoldAnimationController mLauncherUnfoldAnimationController;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        addMultiWindowModeChangedListener(mDepthController);
-        initUnfoldTransitionProgressProvider();
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-
-        if (mLauncherUnfoldAnimationController != null) {
-            mLauncherUnfoldAnimationController.onResume();
-        }
-    }
-
-    @Override
-    protected void onPause() {
-        if (mLauncherUnfoldAnimationController != null) {
-            mLauncherUnfoldAnimationController.onPause();
-        }
-
-        super.onPause();
-    }
-
-    @Override
-    public void onDestroy() {
-        mAppTransitionManager.onActivityDestroyed();
-        if (mUnfoldTransitionProgressProvider != null) {
-            mUnfoldTransitionProgressProvider.destroy();
-        }
-
-        mTISBindHelper.onDestroy();
-        if (mTaskbarManager != null) {
-            mTaskbarManager.clearActivity(this);
-        }
-
-        if (mLauncherUnfoldAnimationController != null) {
-            mLauncherUnfoldAnimationController.onDestroy();
-        }
-
-        super.onDestroy();
-    }
-
-    @Override
-    protected void onNewIntent(Intent intent) {
-        super.onNewIntent(intent);
-
-        if (mOverviewCommandHelper != null) {
-            mOverviewCommandHelper.clearPendingCommands();
-        }
-    }
-
-    public QuickstepTransitionManager getAppTransitionManager() {
-        return mAppTransitionManager;
-    }
-
-    @Override
-    public void onEnterAnimationComplete() {
-        super.onEnterAnimationComplete();
-        // After the transition to home, enable the high-res thumbnail loader if it wasn't enabled
-        // as a part of quickstep, so that high-res thumbnails can load the next time we enter
-        // overview
-        RecentsModel.INSTANCE.get(this).getThumbnailCache()
-                .getHighResLoadingState().setVisible(true);
-    }
-
-    @Override
-    protected void handleGestureContract(Intent intent) {
-        if (FeatureFlags.SEPARATE_RECENTS_ACTIVITY.get()) {
-            super.handleGestureContract(intent);
-        }
-    }
-
-    @Override
-    public void onTrimMemory(int level) {
-        super.onTrimMemory(level);
-        RecentsModel.INSTANCE.get(this).onTrimMemory(level);
-    }
-
-    @Override
-    public void onUiChangedWhileSleeping() {
-        // Remove the snapshot because the content view may have obvious changes.
-        UI_HELPER_EXECUTOR.execute(
-                () -> ActivityManagerWrapper.getInstance().invalidateHomeTaskSnapshot(this));
-    }
-
-    @Override
-    protected void onScreenOff() {
-        super.onScreenOff();
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            RecentsView recentsView = getOverviewPanel();
-            recentsView.finishRecentsAnimation(true /* toRecents */, null);
-        }
-    }
-
-    /**
-     * {@code LauncherOverlayCallbacks} scroll amount.
-     * Indicates transition progress to -1 screen.
-     * @param progress From 0 to 1.
-     */
-    @Override
-    public void onScrollChanged(float progress) {
-        super.onScrollChanged(progress);
-        mDepthController.onOverlayScrollChanged(progress);
-        onTaskbarInAppDisplayProgressUpdate(progress, MINUS_ONE_PAGE_PROGRESS_INDEX);
-    }
-
-    @Override
-    public void onAllAppsTransition(float progress) {
-        super.onAllAppsTransition(progress);
-        onTaskbarInAppDisplayProgressUpdate(progress, ALL_APPS_PAGE_PROGRESS_INDEX);
-    }
-
-    @Override
-    public void onWidgetsTransition(float progress) {
-        super.onWidgetsTransition(progress);
-        onTaskbarInAppDisplayProgressUpdate(progress, WIDGETS_PAGE_PROGRESS_INDEX);
-    }
-
-    private void onTaskbarInAppDisplayProgressUpdate(float progress, int flag) {
-        if (mTaskbarManager == null
-                || mTaskbarManager.getCurrentActivityContext() == null
-                || mTaskbarUIController == null) {
-            return;
-        }
-        mTaskbarUIController.onTaskbarInAppDisplayProgressUpdate(progress, flag);
-    }
-
-    @Override
-    public void startIntentSenderForResult(IntentSender intent, int requestCode,
-            Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options) {
-        if (requestCode != -1) {
-            mPendingActivityRequestCode = requestCode;
-            StartActivityParams params = new StartActivityParams(this, requestCode);
-            params.intentSender = intent;
-            params.fillInIntent = fillInIntent;
-            params.flagsMask = flagsMask;
-            params.flagsValues = flagsValues;
-            params.extraFlags = extraFlags;
-            params.options = options;
-            startActivity(ProxyActivityStarter.getLaunchIntent(this, params));
-        } else {
-            super.startIntentSenderForResult(intent, requestCode, fillInIntent, flagsMask,
-                    flagsValues, extraFlags, options);
-        }
-    }
-
-    @Override
-    public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
-        if (requestCode != -1) {
-            mPendingActivityRequestCode = requestCode;
-            StartActivityParams params = new StartActivityParams(this, requestCode);
-            params.intent = intent;
-            params.options = options;
-            startActivity(ProxyActivityStarter.getLaunchIntent(this, params));
-        } else {
-            super.startActivityForResult(intent, requestCode, options);
-        }
-    }
-
-    @Override
-    protected void onDeferredResumed() {
-        super.onDeferredResumed();
-        handlePendingActivityRequest();
-    }
-
-    @Override
-    public void onStateSetEnd(LauncherState state) {
-        super.onStateSetEnd(state);
-        handlePendingActivityRequest();
-    }
-
-    private void handlePendingActivityRequest() {
-        if (mPendingActivityRequestCode != -1 && isInState(NORMAL)
-                && ((getActivityFlags() & ACTIVITY_STATE_DEFERRED_RESUMED) != 0)) {
-            // Remove any active ProxyActivityStarter task and send RESULT_CANCELED to Launcher.
-            onActivityResult(mPendingActivityRequestCode, RESULT_CANCELED, null);
-            // ProxyActivityStarter is started with clear task to reset the task after which it
-            // removes the task itself.
-            startActivity(ProxyActivityStarter.getLaunchIntent(this, null));
-        }
-    }
-
-    @Override
-    protected void setupViews() {
-        super.setupViews();
-
-        mActionsView = findViewById(R.id.overview_actions_view);
-        RecentsView overviewPanel = (RecentsView) getOverviewPanel();
-        SplitSelectStateController controller =
-                new SplitSelectStateController(this, mHandler, getStateManager(),
-                        getDepthController());
-        overviewPanel.init(mActionsView, controller);
-        mActionsView.updateDimension(getDeviceProfile(), overviewPanel.getLastComputedTaskSize());
-        mActionsView.updateVerticalMargin(DisplayController.getNavigationMode(this));
-
-        mAppTransitionManager = new QuickstepTransitionManager(this);
-        mAppTransitionManager.registerRemoteAnimations();
-        mAppTransitionManager.registerRemoteTransitions();
-
-        mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
-    }
-
-    private void onTISConnected(TISBinder binder) {
-        mTaskbarManager = binder.getTaskbarManager();
-        mTaskbarManager.setActivity(this);
-        mOverviewCommandHelper = binder.getOverviewCommandHelper();
-    }
-
-    @Override
-    public void runOnBindToTouchInteractionService(Runnable r) {
-        mTISBindHelper.runOnBindToTouchInteractionService(r);
-    }
-
-    private void initUnfoldTransitionProgressProvider() {
-        final UnfoldTransitionConfig config = UnfoldTransitionFactory.createConfig(this);
-        if (config.isEnabled()) {
-            mUnfoldTransitionProgressProvider =
-                    UnfoldTransitionFactory.createUnfoldTransitionProgressProvider(
-                            this,
-                            config,
-                            ProxyScreenStatusProvider.INSTANCE,
-                            getSystemService(DeviceStateManager.class),
-                            getSystemService(ActivityManager.class),
-                            getSystemService(SensorManager.class),
-                            getMainThreadHandler(),
-                            getMainExecutor(),
-                            /* backgroundExecutor= */ THREAD_POOL_EXECUTOR,
-                            /* tracingTagPrefix= */ "launcher"
-                    );
-
-            mLauncherUnfoldAnimationController = new LauncherUnfoldAnimationController(
-                    this,
-                    getWindowManager(),
-                    mUnfoldTransitionProgressProvider
-            );
-        }
-    }
-
-    public void setTaskbarUIController(LauncherTaskbarUIController taskbarUIController) {
-        mTaskbarUIController = taskbarUIController;
-    }
-
-    public @Nullable LauncherTaskbarUIController getTaskbarUIController() {
-        return mTaskbarUIController;
-    }
-
-    public <T extends OverviewActionsView> T getActionsView() {
-        return (T) mActionsView;
-    }
-
-    @Override
-    protected void closeOpenViews(boolean animate) {
-        super.closeOpenViews(animate);
-        TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY);
-    }
-
-    @Override
-    protected void collectStateHandlers(List<StateHandler> out) {
-        super.collectStateHandlers(out);
-        out.add(getDepthController());
-        out.add(new RecentsViewStateController(this));
-        out.add(new BackButtonAlphaHandler(this));
-    }
-
-    public DepthController getDepthController() {
-        return mDepthController;
-    }
-
-    @Nullable
-    public UnfoldTransitionProgressProvider getUnfoldTransitionProgressProvider() {
-        return mUnfoldTransitionProgressProvider;
-    }
-
-    @Override
-    public boolean supportsAdaptiveIconAnimation(View clickedView) {
-        return mAppTransitionManager.hasControlRemoteAppTransitionPermission();
-    }
-
-    @Override
-    public DragOptions getDefaultWorkspaceDragOptions() {
-        if (mNextWorkspaceDragOptions != null) {
-            DragOptions options = mNextWorkspaceDragOptions;
-            mNextWorkspaceDragOptions = null;
-            return options;
-        }
-        return super.getDefaultWorkspaceDragOptions();
-    }
-
-    public void setNextWorkspaceDragOptions(DragOptions dragOptions) {
-        mNextWorkspaceDragOptions = dragOptions;
-    }
-
-    @Override
-    public void useFadeOutAnimationForLauncherStart(CancellationSignal signal) {
-        QuickstepTransitionManager appTransitionManager = getAppTransitionManager();
-        appTransitionManager.setRemoteAnimationProvider(new RemoteAnimationProvider() {
-            @Override
-            public AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] appTargets,
-                    RemoteAnimationTargetCompat[] wallpaperTargets) {
-
-                // On the first call clear the reference.
-                signal.cancel();
-
-                ValueAnimator fadeAnimation = ValueAnimator.ofFloat(1, 0);
-                fadeAnimation.addUpdateListener(new RemoteFadeOutAnimationListener(appTargets,
-                        wallpaperTargets));
-                AnimatorSet anim = new AnimatorSet();
-                anim.play(fadeAnimation);
-                return anim;
-            }
-        }, signal);
-    }
-
-    @Override
-    public float[] getNormalOverviewScaleAndOffset() {
-        return DisplayController.getNavigationMode(this).hasGestures
-                ? new float[] {1, 1} : new float[] {1.1f, NO_OFFSET};
-    }
-
-    @Override
-    public void onDragLayerHierarchyChanged() {
-        onLauncherStateOrFocusChanged();
-    }
-
-    @Override
-    protected void onActivityFlagsChanged(int changeBits) {
-        if ((changeBits
-                & (ACTIVITY_STATE_WINDOW_FOCUSED | ACTIVITY_STATE_TRANSITION_ACTIVE)) != 0) {
-            onLauncherStateOrFocusChanged();
-        }
-
-        if ((changeBits & ACTIVITY_STATE_STARTED) != 0) {
-            mDepthController.setActivityStarted(isStarted());
-        }
-
-        if ((changeBits & ACTIVITY_STATE_RESUMED) != 0) {
-            if (mTaskbarUIController != null) {
-                mTaskbarUIController.onLauncherResumedOrPaused(hasBeenResumed());
-            }
-        }
-
-        super.onActivityFlagsChanged(changeBits);
-    }
-
-    public boolean shouldBackButtonBeHidden(LauncherState toState) {
-        NavigationMode mode = DisplayController.getNavigationMode(this);
-        boolean shouldBackButtonBeHidden = mode.hasGestures
-                && toState.hasFlag(FLAG_HIDE_BACK_BUTTON)
-                && hasWindowFocus()
-                && (getActivityFlags() & ACTIVITY_STATE_TRANSITION_ACTIVE) == 0;
-        if (shouldBackButtonBeHidden) {
-            // Show the back button if there is a floating view visible.
-            shouldBackButtonBeHidden = AbstractFloatingView.getTopOpenViewWithType(this,
-                    TYPE_ALL & ~TYPE_HIDE_BACK_BUTTON) == null;
-        }
-        return shouldBackButtonBeHidden;
-    }
-
-    /**
-     * Sets the back button visibility based on the current state/window focus.
-     */
-    private void onLauncherStateOrFocusChanged() {
-        boolean shouldBackButtonBeHidden = shouldBackButtonBeHidden(getStateManager().getState());
-        if (DisplayController.getNavigationMode(this) == TWO_BUTTONS) {
-            UiThreadHelper.setBackButtonAlphaAsync(this, SET_BACK_BUTTON_ALPHA,
-                    shouldBackButtonBeHidden ? 0f : 1f, true /* animate */);
-        }
-        if (getDragLayer() != null) {
-            getRootView().setDisallowBackGesture(shouldBackButtonBeHidden);
-        }
-    }
-
-    @Override
-    public void finishBindingItems(IntSet pagesBoundFirst) {
-        super.finishBindingItems(pagesBoundFirst);
-        // Instantiate and initialize WellbeingModel now that its loading won't interfere with
-        // populating workspace.
-        // TODO: Find a better place for this
-        WellbeingModel.INSTANCE.get(this);
-    }
-
-    @Override
-    public void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks) {
-        pendingTasks.add(() -> {
-            // This is added in pending task as we need to wait for views to be positioned
-            // correctly before registering them for the animation.
-            if (mLauncherUnfoldAnimationController != null) {
-                // This is needed in case items are rebound while the unfold animation is in
-                // progress.
-                mLauncherUnfoldAnimationController.updateRegisteredViewsIfNeeded();
-            }
-        });
-        super.onInitialBindComplete(boundPages, pendingTasks);
-    }
-
-    @Override
-    public Stream<SystemShortcut.Factory> getSupportedShortcuts() {
-        Stream<SystemShortcut.Factory> base = Stream.of(WellbeingModel.SHORTCUT_FACTORY);
-        if (ENABLE_SPLIT_FROM_WORKSPACE.get() && mDeviceProfile.isTablet) {
-            RecentsView recentsView = getOverviewPanel();
-            // TODO: Pull it out of PagedOrentationHandler for split from workspace.
-            List<SplitPositionOption> positions =
-                    recentsView.getPagedOrientationHandler().getSplitPositionOptions(
-                            mDeviceProfile);
-            List<SystemShortcut.Factory<BaseQuickstepLauncher>> splitShortcuts = new ArrayList<>();
-            for (SplitPositionOption position : positions) {
-                splitShortcuts.add(getSplitSelectShortcutByPosition(position));
-            }
-            base = Stream.concat(base, splitShortcuts.stream());
-        }
-        return Stream.concat(base, super.getSupportedShortcuts());
-    }
-
-    @Override
-    public ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) {
-        ActivityOptionsWrapper activityOptions =
-                mAppTransitionManager.hasControlRemoteAppTransitionPermission()
-                        ? mAppTransitionManager.getActivityLaunchOptions(v)
-                        : super.getActivityLaunchOptions(v, item);
-        if (mLastTouchUpTime > 0) {
-            ActivityOptionsCompat.setLauncherSourceInfo(
-                    activityOptions.options, mLastTouchUpTime);
-        }
-        activityOptions.options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON);
-        activityOptions.options.setLaunchDisplayId(
-                (v != null && v.getDisplay() != null) ? v.getDisplay().getDisplayId()
-                        : Display.DEFAULT_DISPLAY);
-        addLaunchCookie(item, activityOptions.options);
-        return activityOptions;
-    }
-
-    /**
-     * Adds a new launch cookie for the activity launch if supported.
-     *
-     * @param info the item info for the launch
-     * @param opts the options to set the launchCookie on.
-     */
-    public void addLaunchCookie(ItemInfo info, ActivityOptions opts) {
-        IBinder launchCookie = getLaunchCookie(info);
-        if (launchCookie != null) {
-            opts.setLaunchCookie(launchCookie);
-        }
-    }
-
-    /**
-     * Return a new launch cookie for the activity launch if supported.
-     *
-     * @param info the item info for the launch
-     */
-    public IBinder getLaunchCookie(ItemInfo info) {
-        if (info == null) {
-            return null;
-        }
-        switch (info.container) {
-            case LauncherSettings.Favorites.CONTAINER_DESKTOP:
-            case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
-                // Fall through and continue it's on the workspace (we don't support swiping back
-                // to other containers like all apps or the hotseat predictions (which can change)
-                break;
-            default:
-                if (info.container >= 0) {
-                    // Also allow swiping to folders
-                    break;
-                }
-                // Reset any existing launch cookies associated with the cookie
-                return ObjectWrapper.wrap(NO_MATCHING_ID);
-        }
-        switch (info.itemType) {
-            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
-            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
-            case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
-            case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
-                // Fall through and continue if it's an app, shortcut, or widget
-                break;
-            default:
-                // Reset any existing launch cookies associated with the cookie
-                return ObjectWrapper.wrap(NO_MATCHING_ID);
-        }
-        return ObjectWrapper.wrap(new Integer(info.id));
-    }
-
-    public void setHintUserWillBeActive() {
-        addActivityFlags(ACTIVITY_STATE_USER_WILL_BE_ACTIVE);
-    }
-
-    @Override
-    public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) {
-        super.onDisplayInfoChanged(context, info, flags);
-        // When changing screens, force moving to rest state similar to StatefulActivity.onStop, as
-        // StatefulActivity isn't called consistently.
-        if ((flags & CHANGE_ACTIVE_SCREEN) != 0) {
-            getStateManager().moveToRestState();
-        }
-
-        if ((flags & CHANGE_NAVIGATION_MODE) != 0) {
-            getDragLayer().recreateControllers();
-            if (mActionsView != null) {
-                mActionsView.updateVerticalMargin(info.navigationMode);
-            }
-        }
-    }
-
-    @Override
-    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
-        super.dump(prefix, fd, writer, args);
-        if (mDepthController != null) {
-            mDepthController.dump(prefix, writer);
-        }
-    }
-}
diff --git a/quickstep/src/com/android/launcher3/LauncherInitListener.java b/quickstep/src/com/android/launcher3/LauncherInitListener.java
index 35151f1..c4e85f6 100644
--- a/quickstep/src/com/android/launcher3/LauncherInitListener.java
+++ b/quickstep/src/com/android/launcher3/LauncherInitListener.java
@@ -20,6 +20,7 @@
 import android.os.Build;
 import android.os.CancellationSignal;
 
+import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.util.RemoteAnimationProvider;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -44,7 +45,7 @@
     public boolean handleInit(Launcher launcher, boolean alreadyOnHome) {
         if (mRemoteAnimationProvider != null) {
             QuickstepTransitionManager appTransitionManager =
-                    ((BaseQuickstepLauncher) launcher).getAppTransitionManager();
+                    ((QuickstepLauncher) launcher).getAppTransitionManager();
 
             // Set a one-time animation provider. After the first call, this will get cleared.
             // TODO: Probably also check the intended target id.
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 9aeb370..9a1ed4d 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;
@@ -43,8 +42,7 @@
 import static com.android.launcher3.config.FeatureFlags.KEYGUARD_ANIMATION;
 import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY;
 import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
-import static com.android.launcher3.statehandlers.DepthController.DEPTH;
-import static com.android.launcher3.testing.TestProtocol.BAD_STATE;
+import static com.android.launcher3.statehandlers.DepthController.STATE_DEPTH;
 import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
 import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
 import static com.android.launcher3.views.FloatingIconView.getFloatingIconView;
@@ -59,6 +57,8 @@
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
+import android.app.ActivityOptions;
+import android.app.WindowConfiguration;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -78,13 +78,14 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.Settings;
-import android.util.Log;
 import android.util.Pair;
 import android.util.Size;
+import android.view.CrossWindowBlurListeners;
 import android.view.SurfaceControl;
 import android.view.View;
 import android.view.ViewRootImpl;
 import android.view.ViewTreeObserver;
+import android.view.WindowManager;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
@@ -96,12 +97,15 @@
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.LauncherAnimationRunner.RemoteAnimationFactory;
 import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.AnimatorListeners;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.icons.FastBitmapDrawable;
 import com.android.launcher3.shortcuts.DeepShortcutView;
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.taskbar.LauncherTaskbarUIController;
+import com.android.launcher3.testing.shared.ResourceUtils;
 import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.util.ActivityOptionsWrapper;
 import com.android.launcher3.util.DynamicResource;
 import com.android.launcher3.util.ObjectWrapper;
@@ -122,8 +126,6 @@
 import com.android.quickstep.util.WorkspaceRevealAnim;
 import com.android.quickstep.views.FloatingWidgetView;
 import com.android.quickstep.views.RecentsView;
-import com.android.systemui.shared.system.ActivityCompat;
-import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.BlurUtils;
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -133,11 +135,9 @@
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.RemoteTransitionCompat;
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
-import com.android.systemui.shared.system.WindowManagerWrapper;
 import com.android.wm.shell.startingsurface.IStartingWindowListener;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.LinkedHashMap;
 import java.util.List;
 
@@ -146,8 +146,6 @@
  */
 public class QuickstepTransitionManager implements OnDeviceProfileChangeListener {
 
-    private static final String TAG = "QuickstepTransition";
-
     private static final boolean ENABLE_SHELL_STARTING_SURFACE =
             SystemProperties.getBoolean("persist.debug.shell_starting_surface", true);
 
@@ -184,6 +182,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;
 
@@ -192,12 +194,11 @@
     // Cross-fade duration between App Widget and App
     private static final int WIDGET_CROSSFADE_DURATION_MILLIS = 125;
 
-    protected final BaseQuickstepLauncher mLauncher;
+    protected final QuickstepLauncher mLauncher;
     private final DragLayer mDragLayer;
 
     final Handler mHandler;
 
-    private final float mContentScale;
     private final float mClosingWindowTransY;
     private final float mMaxShadowRadius;
 
@@ -242,7 +243,6 @@
         mBackAnimationController = new LauncherBackAnimationController(mLauncher, this);
 
         Resources res = mLauncher.getResources();
-        mContentScale = res.getFloat(R.dimen.content_scale);
         mClosingWindowTransY = res.getDimensionPixelSize(R.dimen.closing_window_trans_y);
         mMaxShadowRadius = res.getDimensionPixelSize(R.dimen.max_shadow_radius);
 
@@ -293,8 +293,10 @@
         RemoteAnimationAdapterCompat adapterCompat =
                 new RemoteAnimationAdapterCompat(runner, duration, statusBarTransitionDelay,
                         mLauncher.getIApplicationThread());
-        return new ActivityOptionsWrapper(
-                ActivityOptionsCompat.makeRemoteAnimation(adapterCompat), onEndCallback);
+        ActivityOptions options = ActivityOptions.makeRemoteAnimation(
+                adapterCompat.getWrapped(),
+                adapterCompat.getRemoteTransition().getTransition());
+        return new ActivityOptionsWrapper(options, onEndCallback);
     }
 
     /**
@@ -384,7 +386,7 @@
                 @Override
                 public void onAnimationStart(Animator animation) {
                     mLauncher.addOnResumeCallback(() ->
-                            ObjectAnimator.ofFloat(mLauncher.getDepthController(), DEPTH,
+                            ObjectAnimator.ofFloat(mLauncher.getDepthController(), STATE_DEPTH,
                                     mLauncher.getStateManager().getState().getDepth(
                                             mLauncher)).start());
                 }
@@ -408,7 +410,7 @@
             @Override
             public void onAnimationStart(Animator animation) {
                 mLauncher.addOnResumeCallback(() ->
-                        ObjectAnimator.ofFloat(mLauncher.getDepthController(), DEPTH,
+                        ObjectAnimator.ofFloat(mLauncher.getDepthController(), STATE_DEPTH,
                                 mLauncher.getStateManager().getState().getDepth(
                                         mLauncher)).start());
             }
@@ -445,7 +447,7 @@
                         4 - rotationChange);
             }
         }
-        if (mDeviceProfile.isTaskbarPresentInApps) {
+        if (mDeviceProfile.isTaskbarPresentInApps && !target.willShowImeOnTarget) {
             // Animate to above the taskbar.
             bounds.bottom -= target.contentInsets.bottom;
         }
@@ -480,8 +482,8 @@
                 : new float[]{0, 1};
 
         float[] scales = isAppOpening
-                ? new float[]{1, mContentScale}
-                : new float[]{mContentScale, 1};
+                ? new float[]{1, mDeviceProfile.workspaceContentScale}
+                : new float[]{mDeviceProfile.workspaceContentScale, 1};
 
         // Pause expensive view updates as they can lead to layer thrashing and skipped frames.
         mLauncher.pauseExpensiveViewUpdates();
@@ -528,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);
@@ -614,28 +624,9 @@
         RecentsView overview = mLauncher.getOverviewPanel();
         ObjectAnimator alpha = ObjectAnimator.ofFloat(overview,
                 RecentsView.CONTENT_ALPHA, alphas);
-        Log.d(BAD_STATE, "QTM composeViewContentAnimator alphas=" + Arrays.toString(alphas));
-        alpha.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationStart(Animator animation) {
-                Log.d(BAD_STATE, "QTM composeViewContentAnimator onStart");
-            }
-
-            @Override
-            public void onAnimationCancel(Animator animation) {
-                float alpha = overview == null ? -1 : RecentsView.CONTENT_ALPHA.get(overview);
-                Log.d(BAD_STATE, "QTM composeViewContentAnimator onCancel, alpha=" + alpha);
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                Log.d(BAD_STATE, "QTM composeViewContentAnimator onEnd");
-            }
-        });
         alpha.setDuration(CONTENT_ALPHA_DURATION);
         alpha.setInterpolator(LINEAR);
         anim.play(alpha);
-        Log.d(BAD_STATE, "QTM composeViewContentAnimator setFreezeVisibility=true");
         overview.setFreezeViewVisibility(true);
 
         ObjectAnimator scaleAnim = ObjectAnimator.ofFloat(overview, SCALE_PROPERTY, scales);
@@ -644,7 +635,6 @@
         anim.play(scaleAnim);
 
         return () -> {
-            Log.d(BAD_STATE, "QTM composeViewContentAnimator onEnd setFreezeVisibility=false");
             overview.setFreezeViewVisibility(false);
             SCALE_PROPERTY.set(overview, 1f);
             mLauncher.getStateManager().reapplyState();
@@ -1059,54 +1049,37 @@
     private ObjectAnimator getBackgroundAnimator() {
         // When launching an app from overview that doesn't map to a task, we still want to just
         // blur the wallpaper instead of the launcher surface as well
-        boolean allowBlurringLauncher = mLauncher.getStateManager().getState() != OVERVIEW;
-        DepthController depthController = mLauncher.getDepthController();
-        ObjectAnimator backgroundRadiusAnim = ObjectAnimator.ofFloat(depthController, DEPTH,
-                BACKGROUND_APP.getDepth(mLauncher))
+        boolean allowBlurringLauncher = mLauncher.getStateManager().getState() != OVERVIEW
+                && BlurUtils.supportsBlursOnWindows();
+
+        MyDepthController depthController = new MyDepthController(mLauncher);
+        ObjectAnimator backgroundRadiusAnim = ObjectAnimator.ofFloat(depthController, STATE_DEPTH,
+                        BACKGROUND_APP.getDepth(mLauncher))
                 .setDuration(APP_LAUNCH_DURATION);
+
         if (allowBlurringLauncher) {
-            final SurfaceControl dimLayer;
-            if (BlurUtils.supportsBlursOnWindows()) {
-                // Create a temporary effect layer, that lives on top of launcher, so we can apply
-                // the blur to it. The EffectLayer will be fullscreen, which will help with caching
-                // optimizations on the SurfaceFlinger side:
-                // - Results would be able to be cached as a texture
-                // - There won't be texture allocation overhead, because EffectLayers don't have
-                //   buffers
-                ViewRootImpl viewRootImpl = mLauncher.getDragLayer().getViewRootImpl();
-                SurfaceControl parent = viewRootImpl != null
-                        ? viewRootImpl.getSurfaceControl()
-                        : null;
-                dimLayer = new SurfaceControl.Builder()
-                        .setName("Blur layer")
-                        .setParent(parent)
-                        .setOpaque(false)
-                        .setHidden(false)
-                        .setEffectLayer()
-                        .build();
-            } else {
-                dimLayer = null;
-            }
+            // Create a temporary effect layer, that lives on top of launcher, so we can apply
+            // the blur to it. The EffectLayer will be fullscreen, which will help with caching
+            // optimizations on the SurfaceFlinger side:
+            // - Results would be able to be cached as a texture
+            // - There won't be texture allocation overhead, because EffectLayers don't have
+            //   buffers
+            ViewRootImpl viewRootImpl = mLauncher.getDragLayer().getViewRootImpl();
+            SurfaceControl parent = viewRootImpl != null
+                    ? viewRootImpl.getSurfaceControl()
+                    : null;
+            SurfaceControl dimLayer = new SurfaceControl.Builder()
+                    .setName("Blur layer")
+                    .setParent(parent)
+                    .setOpaque(false)
+                    .setHidden(false)
+                    .setEffectLayer()
+                    .build();
 
-            depthController.setSurface(dimLayer);
-            backgroundRadiusAnim.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationStart(Animator animation) {
-                    depthController.setIsInLaunchTransition(true);
-                }
-
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    depthController.setIsInLaunchTransition(false);
-                    depthController.setSurface(null);
-                    if (dimLayer != null) {
-                        new SurfaceControl.Transaction()
-                                .remove(dimLayer)
-                                .apply();
-                    }
-                }
-            });
+            backgroundRadiusAnim.addListener(AnimatorListeners.forEndCallback(() ->
+                    new SurfaceControl.Transaction().remove(dimLayer).apply()));
         }
+
         return backgroundRadiusAnim;
     }
 
@@ -1121,8 +1094,8 @@
             mWallpaperOpenRunner = createWallpaperOpenRunner(false /* fromUnlock */);
 
             RemoteAnimationDefinitionCompat definition = new RemoteAnimationDefinitionCompat();
-            definition.addRemoteAnimation(WindowManagerWrapper.TRANSIT_WALLPAPER_OPEN,
-                    WindowManagerWrapper.ACTIVITY_TYPE_STANDARD,
+            definition.addRemoteAnimation(WindowManager.TRANSIT_OLD_WALLPAPER_OPEN,
+                    WindowConfiguration.ACTIVITY_TYPE_STANDARD,
                     new RemoteAnimationAdapterCompat(
                             new LauncherAnimationRunner(mHandler, mWallpaperOpenRunner,
                                     false /* startAtFrontOfQueue */),
@@ -1132,7 +1105,7 @@
             if (KEYGUARD_ANIMATION.get()) {
                 mKeyguardGoingAwayRunner = createWallpaperOpenRunner(true /* fromUnlock */);
                 definition.addRemoteAnimation(
-                        WindowManagerWrapper.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
+                        WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
                         new RemoteAnimationAdapterCompat(
                                 new LauncherAnimationRunner(
                                         mHandler, mKeyguardGoingAwayRunner,
@@ -1141,7 +1114,7 @@
                                 mLauncher.getIApplicationThread()));
             }
 
-            new ActivityCompat(mLauncher).registerRemoteAnimations(definition);
+            mLauncher.registerRemoteAnimations(definition.getWrapped());
         }
     }
 
@@ -1177,7 +1150,7 @@
             return;
         }
         if (hasControlRemoteAppTransitionPermission()) {
-            new ActivityCompat(mLauncher).unregisterRemoteAnimations();
+            mLauncher.unregisterRemoteAnimations();
 
             // Also clear strong references to the runners registered with the remote animation
             // definition so we don't have to wait for the system gc
@@ -1448,7 +1421,7 @@
         animation.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
-                anim.start(mLauncher, velocityPxPerS);
+                anim.start(mLauncher, mDeviceProfile, velocityPxPerS);
             }
         });
         return anim;
@@ -1623,7 +1596,8 @@
                             true /* animateOverviewScrim */, launcherView).getAnimators());
 
                     if (!areAllTargetsTranslucent(appTargets)) {
-                        anim.play(ObjectAnimator.ofFloat(mLauncher.getDepthController(), DEPTH,
+                        anim.play(ObjectAnimator.ofFloat(mLauncher.getDepthController(),
+                                STATE_DEPTH,
                                 BACKGROUND_APP.getDepth(mLauncher), NORMAL.getDepth(mLauncher)));
                     }
 
@@ -1951,4 +1925,17 @@
             return Utilities.mapToRange(progress, start, end, 1, 0, ACCEL_1_5);
         }
     }
+
+    private static class MyDepthController extends DepthController {
+        MyDepthController(Launcher l) {
+            super(l);
+            setCrossWindowBlursEnabled(
+                    CrossWindowBlurListeners.getInstance().isCrossWindowBlurEnabled());
+        }
+
+        @Override
+        public void setSurface(SurfaceControl surface) {
+            super.setSurface(surface);
+        }
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
index f42b39f..e8374b8 100644
--- a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
@@ -35,6 +35,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.FloatingHeaderRow;
 import com.android.launcher3.allapps.FloatingHeaderView;
+import com.android.launcher3.util.OnboardingPrefs;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.ActivityContext;
 
@@ -92,8 +93,10 @@
                 ? R.color.all_apps_label_text_dark
                 : R.color.all_apps_label_text);
 
-        mShowAllAppsLabel = !ActivityContext.lookupContext(
-                getContext()).getOnboardingPrefs().hasReachedMaxCount(ALL_APPS_VISITED_COUNT);
+        OnboardingPrefs<?> onboardingPrefs = ActivityContext.lookupContext(
+                getContext()).getOnboardingPrefs();
+        mShowAllAppsLabel = onboardingPrefs == null || !onboardingPrefs.hasReachedMaxCount(
+                ALL_APPS_VISITED_COUNT);
     }
 
     public void setup(FloatingHeaderView parent, FloatingHeaderRow[] rows, boolean tabsHidden) {
@@ -216,8 +219,8 @@
 
             CharSequence allAppsLabelText = getResources().getText(R.string.all_apps_label);
             mAllAppsLabelLayout = StaticLayout.Builder.obtain(
-                    allAppsLabelText, 0, allAppsLabelText.length(), mPaint,
-                    Math.round(mPaint.measureText(allAppsLabelText.toString())))
+                            allAppsLabelText, 0, allAppsLabelText.length(), mPaint,
+                            Math.round(mPaint.measureText(allAppsLabelText.toString())))
                     .setAlignment(Layout.Alignment.ALIGN_CENTER)
                     .setMaxLines(1)
                     .setIncludePad(true)
diff --git a/quickstep/src/com/android/launcher3/appprediction/InstantAppItemInfo.java b/quickstep/src/com/android/launcher3/appprediction/InstantAppItemInfo.java
index 9c3b881..8baee00 100644
--- a/quickstep/src/com/android/launcher3/appprediction/InstantAppItemInfo.java
+++ b/quickstep/src/com/android/launcher3/appprediction/InstantAppItemInfo.java
@@ -22,6 +22,8 @@
 import android.content.Context;
 import android.content.Intent;
 
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -33,11 +35,13 @@
         this.componentName = new ComponentName(packageName, COMPONENT_CLASS_MARKER);
     }
 
+    @NonNull
     @Override
     public ComponentName getTargetComponent() {
         return componentName;
     }
 
+    @NonNull
     @Override
     public WorkspaceItemInfo makeWorkspaceItem(Context context) {
         WorkspaceItemInfo workspaceItemInfo = super.makeWorkspaceItem(context);
diff --git a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
index 351a3bc..c54d119 100644
--- a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -33,6 +33,7 @@
 import com.android.launcher3.DeviceProfile.DeviceProfileListenable;
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.FloatingHeaderRow;
 import com.android.launcher3.allapps.FloatingHeaderView;
 import com.android.launcher3.anim.AlphaUpdateListener;
@@ -117,9 +118,14 @@
 
     @Override
     public int getExpectedHeight() {
-        return getVisibility() == GONE ? 0
-                : mActivityContext.getDeviceProfile().allAppsCellHeightPx + getPaddingTop()
-                        + getPaddingBottom();
+        DeviceProfile deviceProfile = mActivityContext.getDeviceProfile();
+        int iconHeight = deviceProfile.allAppsIconSizePx;
+        int iconPadding = deviceProfile.allAppsIconDrawablePaddingPx;
+        int textHeight = Utilities.calculateTextHeight(deviceProfile.allAppsIconTextSizePx);
+        int verticalPadding = getResources().getDimensionPixelSize(
+                R.dimen.all_apps_predicted_icon_vertical_padding);
+        int totalHeight = iconHeight + iconPadding + textHeight + verticalPadding * 2;
+        return getVisibility() == GONE ? 0 : totalHeight + getPaddingTop() + getPaddingBottom();
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
index d63bc18..048243e 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
@@ -26,22 +26,17 @@
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.Hotseat;
-import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.util.GridOccupancy;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.views.ArrowTipView;
 import com.android.launcher3.views.Snackbar;
 
-import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.stream.IntStream;
@@ -74,102 +69,13 @@
      */
     void migrate() {
         HotseatRestoreHelper.createBackup(mLauncher);
-        if (FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get()) {
-            migrateToFolder();
-        } else {
-            migrateHotseatWhole();
-        }
+        migrateHotseatWhole();
         Snackbar.show(mLauncher, R.string.hotsaet_tip_prediction_enabled,
                 R.string.hotseat_prediction_settings, null,
                 () -> mLauncher.startActivity(getSettingsIntent()));
     }
 
     /**
-     * This migration places all non folder items in the hotseat into a folder and then moves
-     * all folders in the hotseat to a workspace page that has enough empty spots.
-     *
-     * @return pageId that has accepted the items.
-     */
-    private int migrateToFolder() {
-        ArrayDeque<FolderInfo> folders = new ArrayDeque<>();
-        ArrayList<WorkspaceItemInfo> putIntoFolder = new ArrayList<>();
-
-        //separate folders and items that can get in folders
-        for (int i = 0; i < mLauncher.getDeviceProfile().numShownHotseatIcons; i++) {
-            View view = mHotseat.getChildAt(i, 0);
-            if (view == null) continue;
-            ItemInfo info = (ItemInfo) view.getTag();
-            if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
-                folders.add((FolderInfo) info);
-            } else if (info instanceof WorkspaceItemInfo && info.container == LauncherSettings
-                    .Favorites.CONTAINER_HOTSEAT) {
-                putIntoFolder.add((WorkspaceItemInfo) info);
-            }
-        }
-
-        // create a temp folder and add non folder items to it
-        if (!putIntoFolder.isEmpty()) {
-            ItemInfo firstItem = putIntoFolder.get(0);
-            FolderInfo folderInfo = new FolderInfo();
-            mLauncher.getModelWriter().addItemToDatabase(folderInfo, firstItem.container,
-                    firstItem.screenId, firstItem.cellX, firstItem.cellY);
-            folderInfo.setTitle("", mLauncher.getModelWriter());
-            folderInfo.contents.addAll(putIntoFolder);
-            for (int i = 0; i < folderInfo.contents.size(); i++) {
-                ItemInfo item = folderInfo.contents.get(i);
-                item.rank = i;
-                mLauncher.getModelWriter().moveItemInDatabase(item, folderInfo.id, 0,
-                        item.cellX, item.cellY);
-            }
-            folders.add(folderInfo);
-        }
-        mNewItems.addAll(folders);
-
-        return placeFoldersInWorkspace(folders);
-    }
-
-    private int placeFoldersInWorkspace(ArrayDeque<FolderInfo> folders) {
-        if (folders.isEmpty()) return 0;
-
-        Workspace<?> workspace = mLauncher.getWorkspace();
-        InvariantDeviceProfile idp = mLauncher.getDeviceProfile().inv;
-
-        GridOccupancy[] occupancyList = new GridOccupancy[workspace.getChildCount()];
-        for (int i = 0; i < occupancyList.length; i++) {
-            occupancyList[i] = ((CellLayout) workspace.getChildAt(i)).cloneGridOccupancy();
-        }
-        //scan every screen to find available spots to place folders
-        int occupancyIndex = 0;
-        int[] itemXY = new int[2];
-        while (occupancyIndex < occupancyList.length && !folders.isEmpty()) {
-            GridOccupancy occupancy = occupancyList[occupancyIndex];
-            if (occupancy.findVacantCell(itemXY, 1, 1)) {
-                FolderInfo info = folders.poll();
-                mLauncher.getModelWriter().moveItemInDatabase(info,
-                        LauncherSettings.Favorites.CONTAINER_DESKTOP,
-                        workspace.getScreenIdForPageIndex(occupancyIndex), itemXY[0], itemXY[1]);
-                occupancy.markCells(info, true);
-            } else {
-                occupancyIndex++;
-            }
-        }
-        if (folders.isEmpty()) return workspace.getScreenIdForPageIndex(occupancyIndex);
-        int screenId = LauncherSettings.Settings.call(mLauncher.getContentResolver(),
-                LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
-                .getInt(LauncherSettings.Settings.EXTRA_VALUE);
-        // if all screens are full and we still have folders left, put those on a new page
-        FolderInfo folderInfo;
-        int col = 0;
-        while ((folderInfo = folders.poll()) != null) {
-            mLauncher.getModelWriter().moveItemInDatabase(folderInfo,
-                    LauncherSettings.Favorites.CONTAINER_DESKTOP, screenId, col++,
-                    idp.numRows - 1);
-        }
-        mNewScreens = IntArray.wrap(screenId);
-        return workspace.getPageCount();
-    }
-
-    /**
      * This migration option attempts to move the entire hotseat up to the first workspace that
      * has space to host items. If no such page is found, it moves items to a new page.
      *
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
index 119ae90..2100834 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
@@ -15,8 +15,7 @@
  */
 package com.android.launcher3.hybridhotseat;
 
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent
-        .LAUNCHER_HOTSEAT_EDU_ACCEPT;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_EDU_ACCEPT;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_EDU_DENY;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_EDU_SEEN;
 
@@ -39,9 +38,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.uioverrides.ApiWrapper;
 import com.android.launcher3.uioverrides.PredictedAppIcon;
 import com.android.launcher3.views.AbstractSlideInView;
 
@@ -107,19 +104,12 @@
         mDismissBtn.setOnClickListener(this::onDismiss);
 
         LinearLayout buttonContainer = findViewById(R.id.button_container);
-        int adjustedMarginEnd = ApiWrapper.getHotseatEndOffset(context)
-                - buttonContainer.getPaddingEnd();
+        int adjustedMarginEnd = grid.hotseatBarEndOffset - buttonContainer.getPaddingEnd();
         if (InvariantDeviceProfile.INSTANCE.get(context)
                 .getDeviceProfile(context).isTaskbarPresent && adjustedMarginEnd > 0) {
             ((LinearLayout.LayoutParams) buttonContainer.getLayoutParams()).setMarginEnd(
                     adjustedMarginEnd);
         }
-
-        // update ui to reflect which migration method is going to be used
-        if (FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get()) {
-            ((TextView) findViewById(R.id.hotseat_edu_content)).setText(
-                    R.string.hotseat_edu_message_migrate_alt);
-        }
     }
 
     private void onAccept(View v) {
diff --git a/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java b/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java
index 7e3ee7d..bc3253f 100644
--- a/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java
+++ b/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java
@@ -27,6 +27,8 @@
 import android.content.pm.ShortcutInfo;
 import android.os.UserHandle;
 
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.model.BgDataModel.FixedContainerItems;
@@ -52,7 +54,8 @@
     }
 
     @Override
-    public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+    public void execute(@NonNull final LauncherAppState app, @NonNull final BgDataModel dataModel,
+            @NonNull final AllAppsList apps) {
         Context context = app.getContext();
 
         // TODO: remove this
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index 770dfb2..de0b14d 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -117,14 +117,15 @@
         // TODO: Implement caching and preloading
         super.loadItems(ums, pinnedShortcuts);
 
-        WorkspaceItemFactory allAppsFactory = new WorkspaceItemFactory(
-                mApp, ums, pinnedShortcuts, mIDP.numDatabaseAllAppsColumns);
-        FixedContainerItems allAppsItems = new FixedContainerItems(mAllAppsState.containerId,
-                mAllAppsState.storage.read(mApp.getContext(), allAppsFactory, ums.allUsers::get));
-        mDataModel.extraItems.put(mAllAppsState.containerId, allAppsItems);
+        WorkspaceItemFactory allAppsFactory = new WorkspaceItemFactory(mApp, ums, pinnedShortcuts,
+                mIDP.numDatabaseAllAppsColumns, mAllAppsState.containerId);
+        FixedContainerItems allAppsPredictionItems = new FixedContainerItems(
+                mAllAppsState.containerId, mAllAppsState.storage.read(mApp.getContext(),
+                allAppsFactory, ums.allUsers::get));
+        mDataModel.extraItems.put(mAllAppsState.containerId, allAppsPredictionItems);
 
-        WorkspaceItemFactory hotseatFactory =
-                new WorkspaceItemFactory(mApp, ums, pinnedShortcuts, mIDP.numDatabaseHotseatIcons);
+        WorkspaceItemFactory hotseatFactory = new WorkspaceItemFactory(mApp, ums, pinnedShortcuts,
+                mIDP.numDatabaseHotseatIcons, mHotseatState.containerId);
         FixedContainerItems hotseatItems = new FixedContainerItems(mHotseatState.containerId,
                 mHotseatState.storage.read(mApp.getContext(), hotseatFactory, ums.allUsers::get));
         mDataModel.extraItems.put(mHotseatState.containerId, hotseatItems);
@@ -432,15 +433,17 @@
         private final UserManagerState mUMS;
         private final Map<ShortcutKey, ShortcutInfo> mPinnedShortcuts;
         private final int mMaxCount;
+        private final int mContainer;
 
         private int mReadCount = 0;
 
         protected WorkspaceItemFactory(LauncherAppState appState, UserManagerState ums,
-                Map<ShortcutKey, ShortcutInfo> pinnedShortcuts, int maxCount) {
+                Map<ShortcutKey, ShortcutInfo> pinnedShortcuts, int maxCount, int container) {
             mAppState = appState;
             mUMS = ums;
             mPinnedShortcuts = pinnedShortcuts;
             mMaxCount = maxCount;
+            mContainer = container;
         }
 
         @Nullable
@@ -458,6 +461,7 @@
                         return null;
                     }
                     AppInfo info = new AppInfo(lai, user, mUMS.isUserQuiet(user));
+                    info.container = mContainer;
                     mAppState.getIconCache().getTitleAndIcon(info, lai, false);
                     mReadCount++;
                     return info.makeWorkspaceItem(mAppState.getContext());
@@ -472,6 +476,7 @@
                         return null;
                     }
                     WorkspaceItemInfo wii = new WorkspaceItemInfo(si, mAppState.getContext());
+                    wii.container = mContainer;
                     mAppState.getIconCache().getShortcutIcon(wii, si);
                     mReadCount++;
                     return wii;
diff --git a/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java b/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
index 9cd9d85..7a483a8 100644
--- a/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
+++ b/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
@@ -21,6 +21,8 @@
 import android.content.ComponentName;
 import android.text.TextUtils;
 
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.BgDataModel.FixedContainerItems;
@@ -52,7 +54,8 @@
      * workspace.
      */
     @Override
-    public void execute(LauncherAppState appState, BgDataModel dataModel, AllAppsList apps) {
+    public void execute(@NonNull final LauncherAppState appState,
+            @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) {
         Set<ComponentKey> widgetsInWorkspace = dataModel.appWidgets.stream().map(
                 widget -> new ComponentKey(widget.providerName, widget.user)).collect(
                 Collectors.toSet());
diff --git a/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java b/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java
index 4e59790..7c3281a 100644
--- a/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java
+++ b/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.popup;
 
+import static com.android.launcher3.util.SplitConfigurationOptions.getLogEventForPosition;
+
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.graphics.drawable.BitmapDrawable;
@@ -22,9 +24,10 @@
 import android.util.Log;
 import android.view.View;
 
-import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
 import com.android.quickstep.views.RecentsView;
 
@@ -32,18 +35,18 @@
 
     String TAG = QuickstepSystemShortcut.class.getSimpleName();
 
-    static SystemShortcut.Factory<BaseQuickstepLauncher> getSplitSelectShortcutByPosition(
+    static SystemShortcut.Factory<QuickstepLauncher> getSplitSelectShortcutByPosition(
             SplitPositionOption position) {
         return (activity, itemInfo, originalView) ->
                 new QuickstepSystemShortcut.SplitSelectSystemShortcut(activity, itemInfo,
                         originalView, position);
     }
 
-    class SplitSelectSystemShortcut extends SystemShortcut<BaseQuickstepLauncher> {
+    class SplitSelectSystemShortcut extends SystemShortcut<QuickstepLauncher> {
 
         private final SplitPositionOption mPosition;
 
-        public SplitSelectSystemShortcut(BaseQuickstepLauncher launcher, ItemInfo itemInfo,
+        public SplitSelectSystemShortcut(QuickstepLauncher launcher, ItemInfo itemInfo,
                 View originalView, SplitPositionOption position) {
             super(position.iconResId, position.textResId, launcher, itemInfo, originalView);
 
@@ -52,6 +55,7 @@
 
         @Override
         public void onClick(View view) {
+            // Initiate splitscreen from the Home screen or Home All Apps
             Bitmap bitmap;
             Intent intent;
             if (mItemInfo instanceof WorkspaceItemInfo) {
@@ -69,9 +73,10 @@
             }
 
             RecentsView recentsView = mTarget.getOverviewPanel();
+            StatsLogManager.EventEnum splitEvent = getLogEventForPosition(mPosition.stagePosition);
             recentsView.initiateSplitSelect(
                     new SplitSelectSource(mOriginalView, new BitmapDrawable(bitmap), intent,
-                            mPosition));
+                            mPosition, mItemInfo, splitEvent));
         }
     }
 
@@ -81,13 +86,18 @@
         public final Drawable drawable;
         public final Intent intent;
         public final SplitPositionOption position;
+        public final ItemInfo mItemInfo;
+        public final StatsLogManager.EventEnum splitEvent;
 
         public SplitSelectSource(View view, Drawable drawable, Intent intent,
-                SplitPositionOption position) {
+                SplitPositionOption position, ItemInfo itemInfo,
+                StatsLogManager.EventEnum splitEvent) {
             this.view = view;
             this.drawable = drawable;
             this.intent = intent;
             this.position = position;
+            this.mItemInfo = itemInfo;
+            this.splitEvent = splitEvent;
         }
     }
 }
diff --git a/quickstep/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictionsImpl.java b/quickstep/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictionsImpl.java
new file mode 100644
index 0000000..5bf727a
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictionsImpl.java
@@ -0,0 +1,54 @@
+/*
+ * 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.secondarydisplay;
+
+import static com.android.launcher3.util.OnboardingPrefs.ALL_APPS_VISITED_COUNT;
+
+import android.content.Context;
+
+import com.android.launcher3.appprediction.AppsDividerView;
+import com.android.launcher3.appprediction.PredictionRowView;
+import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.util.OnboardingPrefs;
+import com.android.launcher3.views.ActivityContext;
+
+/**
+ * Implementation of SecondaryDisplayPredictions.
+ */
+@SuppressWarnings("unused")
+public final class SecondaryDisplayPredictionsImpl extends SecondaryDisplayPredictions {
+    private final ActivityContext mActivityContext;
+
+    public SecondaryDisplayPredictionsImpl(Context context) {
+        mActivityContext = ActivityContext.lookupContext(context);
+    }
+
+    @Override
+    void updateAppDivider() {
+        OnboardingPrefs<?> onboardingPrefs = mActivityContext.getOnboardingPrefs();
+        mActivityContext.getAppsView().getFloatingHeaderView()
+                .findFixedRowByType(AppsDividerView.class)
+                .setShowAllAppsLabel(!onboardingPrefs.hasReachedMaxCount(ALL_APPS_VISITED_COUNT));
+        onboardingPrefs.incrementEventCount(ALL_APPS_VISITED_COUNT);
+    }
+
+    @Override
+    public void setPredictedApps(BgDataModel.FixedContainerItems item) {
+        mActivityContext.getAppsView().getFloatingHeaderView()
+                .findFixedRowByType(PredictionRowView.class)
+                .setPredictedApps(item.items);
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/statehandlers/BackButtonAlphaHandler.java b/quickstep/src/com/android/launcher3/statehandlers/BackButtonAlphaHandler.java
deleted file mode 100644
index 07d3a51..0000000
--- a/quickstep/src/com/android/launcher3/statehandlers/BackButtonAlphaHandler.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2018 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.statehandlers;
-
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.util.DisplayController.NavigationMode.TWO_BUTTONS;
-import static com.android.quickstep.AnimatedFloat.VALUE;
-
-import com.android.launcher3.BaseQuickstepLauncher;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.statemanager.StateManager.StateHandler;
-import com.android.launcher3.states.StateAnimationConfig;
-import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.UiThreadHelper;
-import com.android.quickstep.AnimatedFloat;
-import com.android.quickstep.SystemUiProxy;
-
-/**
- * State handler for animating back button alpha in two-button nav mode.
- */
-public class BackButtonAlphaHandler implements StateHandler<LauncherState> {
-
-    private final BaseQuickstepLauncher mLauncher;
-    private final AnimatedFloat mBackAlpha = new AnimatedFloat(this::updateBackAlpha);
-
-    public BackButtonAlphaHandler(BaseQuickstepLauncher launcher) {
-        mLauncher = launcher;
-    }
-
-    @Override
-    public void setState(LauncherState state) { }
-
-    @Override
-    public void setStateWithAnimation(LauncherState toState, StateAnimationConfig config,
-            PendingAnimation animation) {
-        if (DisplayController.getNavigationMode(mLauncher) != TWO_BUTTONS) {
-            return;
-        }
-
-        mBackAlpha.value = SystemUiProxy.INSTANCE.get(mLauncher).getLastNavButtonAlpha();
-        animation.setFloat(mBackAlpha, VALUE,
-                mLauncher.shouldBackButtonBeHidden(toState) ? 0 : 1, LINEAR);
-    }
-
-    private void updateBackAlpha() {
-        UiThreadHelper.setBackButtonAlphaAsync(mLauncher,
-                BaseQuickstepLauncher.SET_BACK_BUTTON_ALPHA, mBackAlpha.value, false /* animate */);
-    }
-}
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
index eda0823..e3fd3f9 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
@@ -23,13 +23,8 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
-import android.app.WallpaperManager;
-import android.os.IBinder;
-import android.os.SystemProperties;
 import android.util.FloatProperty;
-import android.view.AttachedSurfaceControl;
 import android.view.CrossWindowBlurListeners;
-import android.view.SurfaceControl;
 import android.view.View;
 import android.view.ViewRootImpl;
 import android.view.ViewTreeObserver;
@@ -37,12 +32,11 @@
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.statemanager.StateManager.StateHandler;
 import com.android.launcher3.states.StateAnimationConfig;
-import com.android.systemui.shared.system.BlurUtils;
+import com.android.quickstep.util.BaseDepthController;
 
 import java.io.PrintWriter;
 import java.util.function.Consumer;
@@ -50,23 +44,9 @@
 /**
  * Controls blur and wallpaper zoom, for the Launcher surface only.
  */
-public class DepthController implements StateHandler<LauncherState>,
+public class DepthController extends BaseDepthController implements StateHandler<LauncherState>,
         BaseActivity.MultiWindowModeChangedListener {
 
-    private static final boolean OVERLAY_SCROLL_ENABLED = false;
-    public static final FloatProperty<DepthController> DEPTH =
-            new FloatProperty<DepthController>("depth") {
-                @Override
-                public void setValue(DepthController depthController, float depth) {
-                    depthController.setDepth(depth);
-                }
-
-                @Override
-                public Float get(DepthController depthController) {
-                    return depthController.mDepth;
-                }
-            };
-
     /**
      * A property that updates the background blur within a given range of values (ie. even if the
      * animator goes beyond 0..1, the interpolated value will still be bounded).
@@ -92,96 +72,46 @@
         }
     }
 
-    private final ViewTreeObserver.OnDrawListener mOnDrawListener =
-            new ViewTreeObserver.OnDrawListener() {
-                @Override
-                public void onDraw() {
-                    View view = mLauncher.getDragLayer();
-                    ViewRootImpl viewRootImpl = view.getViewRootImpl();
-                    boolean applied = setSurface(
-                            viewRootImpl != null ? viewRootImpl.getSurfaceControl() : null);
-                    if (!applied) {
-                        dispatchTransactionSurface(mDepth);
-                    }
-                    view.post(() -> view.getViewTreeObserver().removeOnDrawListener(this));
-                }
-            };
+    private final ViewTreeObserver.OnDrawListener mOnDrawListener = this::onLauncherDraw;
 
-    private final Consumer<Boolean> mCrossWindowBlurListener = new Consumer<Boolean>() {
-        @Override
-        public void accept(Boolean enabled) {
-            mCrossWindowBlursEnabled = enabled;
-            dispatchTransactionSurface(mDepth);
-        }
-    };
+    private final Consumer<Boolean> mCrossWindowBlurListener = this::setCrossWindowBlursEnabled;
 
-    private final Runnable mOpaquenessListener = new Runnable() {
-        @Override
-        public void run() {
-            dispatchTransactionSurface(mDepth);
-        }
-    };
+    private final Runnable mOpaquenessListener = this::applyDepthAndBlur;
 
-    private final Launcher mLauncher;
-    /**
-     * Blur radius when completely zoomed out, in pixels.
-     */
-    private int mMaxBlurRadius;
-    private boolean mCrossWindowBlursEnabled;
-    private WallpaperManager mWallpaperManager;
-    private SurfaceControl mSurface;
-    /**
-     * How visible the -1 overlay is, from 0 to 1.
-     */
-    private float mOverlayScrollProgress;
-    /**
-     * Ratio from 0 to 1, where 0 is fully zoomed out, and 1 is zoomed in.
-     * @see android.service.wallpaper.WallpaperService.Engine#onZoomChanged(float)
-     */
-    private float mDepth;
-    /**
-     * Last blur value, in pixels, that was applied.
-     * For debugging purposes.
-     */
-    private int mCurrentBlur;
     /**
      * If we're launching and app and should not be blurring the screen for performance reasons.
      */
     private boolean mBlurDisabledForAppLaunch;
-    /**
-     * If we requested early wake-up offsets to SurfaceFlinger.
-     */
-    private boolean mInEarlyWakeUp;
+
 
     // Workaround for animating the depth when multiwindow mode changes.
     private boolean mIgnoreStateChangesDuringMultiWindowAnimation = false;
 
-    // Hints that there is potentially content behind Launcher and that we shouldn't optimize by
-    // marking the launcher surface as opaque.  Only used in certain Launcher states.
-    private boolean mHasContentBehindLauncher;
-
     private View.OnAttachStateChangeListener mOnAttachListener;
 
     public DepthController(Launcher l) {
-        mLauncher = l;
+        super(l);
+    }
+
+    private void onLauncherDraw() {
+        View view = mLauncher.getDragLayer();
+        ViewRootImpl viewRootImpl = view.getViewRootImpl();
+        setSurface(viewRootImpl != null ? viewRootImpl.getSurfaceControl() : null);
+        view.post(() -> view.getViewTreeObserver().removeOnDrawListener(mOnDrawListener));
     }
 
     private void ensureDependencies() {
-        if (mWallpaperManager == null) {
-            mMaxBlurRadius = mLauncher.getResources().getInteger(R.integer.max_depth_blur_radius);
-            mWallpaperManager = mLauncher.getSystemService(WallpaperManager.class);
-        }
-
         if (mLauncher.getRootView() != null && mOnAttachListener == null) {
+            View rootView = mLauncher.getRootView();
             mOnAttachListener = new View.OnAttachStateChangeListener() {
                 @Override
                 public void onViewAttachedToWindow(View view) {
+                    CrossWindowBlurListeners.getInstance().addListener(mLauncher.getMainExecutor(),
+                            mCrossWindowBlurListener);
+                    mLauncher.getScrimView().addOpaquenessListener(mOpaquenessListener);
+
                     // To handle the case where window token is invalid during last setDepth call.
-                    IBinder windowToken = mLauncher.getRootView().getWindowToken();
-                    if (windowToken != null) {
-                        mWallpaperManager.setWallpaperZoomOut(windowToken, mDepth);
-                    }
-                    onAttached();
+                    applyDepthAndBlur();
                 }
 
                 @Override
@@ -190,23 +120,13 @@
                     mLauncher.getScrimView().removeOpaquenessListener(mOpaquenessListener);
                 }
             };
-            mLauncher.getRootView().addOnAttachStateChangeListener(mOnAttachListener);
-            if (mLauncher.getRootView().isAttachedToWindow()) {
-                onAttached();
+            rootView.addOnAttachStateChangeListener(mOnAttachListener);
+            if (rootView.isAttachedToWindow()) {
+                mOnAttachListener.onViewAttachedToWindow(rootView);
             }
         }
     }
 
-    private void onAttached() {
-        CrossWindowBlurListeners.getInstance().addListener(mLauncher.getMainExecutor(),
-                mCrossWindowBlurListener);
-        mLauncher.getScrimView().addOpaquenessListener(mOpaquenessListener);
-    }
-
-    public void setHasContentBehindLauncher(boolean hasContentBehindLauncher) {
-        mHasContentBehindLauncher = hasContentBehindLauncher;
-    }
-
     /**
      * Sets if the underlying activity is started or not
      */
@@ -219,29 +139,9 @@
         }
     }
 
-    /**
-     * Sets the specified app target surface to apply the blur to.
-     * @return true when surface was valid and transaction was dispatched.
-     */
-    public boolean setSurface(SurfaceControl surface) {
-        // Set launcher as the SurfaceControl when we don't need an external target anymore.
-        if (surface == null) {
-            ViewRootImpl viewRootImpl = mLauncher.getDragLayer().getViewRootImpl();
-            surface = viewRootImpl != null ? viewRootImpl.getSurfaceControl() : null;
-        }
-        if (mSurface != surface) {
-            mSurface = surface;
-            if (surface != null) {
-                dispatchTransactionSurface(mDepth);
-                return true;
-            }
-        }
-        return false;
-    }
-
     @Override
     public void setState(LauncherState toState) {
-        if (mSurface == null || mIgnoreStateChangesDuringMultiWindowAnimation) {
+        if (mIgnoreStateChangesDuringMultiWindowAnimation) {
             return;
         }
 
@@ -249,7 +149,7 @@
         if (Float.compare(mDepth, toDepth) != 0) {
             setDepth(toDepth);
         } else if (toState == LauncherState.OVERVIEW) {
-            dispatchTransactionSurface(mDepth);
+            applyDepthAndBlur();
         } else if (toState == LauncherState.BACKGROUND_APP) {
             mLauncher.getDragLayer().getViewTreeObserver().addOnDrawListener(mOnDrawListener);
         }
@@ -265,101 +165,22 @@
 
         float toDepth = toState.getDepth(mLauncher);
         if (Float.compare(mDepth, toDepth) != 0) {
-            animation.setFloat(this, DEPTH, toDepth, config.getInterpolator(ANIM_DEPTH, LINEAR));
+            animation.setFloat(this, STATE_DEPTH, toDepth,
+                    config.getInterpolator(ANIM_DEPTH, LINEAR));
         }
     }
 
-    /**
-     * If we're launching an app from the home screen.
-     */
-    public void setIsInLaunchTransition(boolean inLaunchTransition) {
-        boolean blurEnabled = SystemProperties.getBoolean("ro.launcher.blur.appLaunch", true);
-        mBlurDisabledForAppLaunch = inLaunchTransition && !blurEnabled;
-        if (!inLaunchTransition) {
-            // Reset depth at the end of the launch animation, so the wallpaper won't be
-            // zoomed out if an app crashes.
-            setDepth(0f);
-        }
-    }
-
-    private void setDepth(float depth) {
-        depth = Utilities.boundToRange(depth, 0, 1);
-        // Round out the depth to dedupe frequent, non-perceptable updates
-        int depthI = (int) (depth * 256);
-        float depthF = depthI / 256f;
-        if (Float.compare(mDepth, depthF) == 0) {
-            return;
-        }
-        dispatchTransactionSurface(depthF);
-        mDepth = depthF;
-    }
-
-    public void onOverlayScrollChanged(float progress) {
-        if (!OVERLAY_SCROLL_ENABLED) {
-            return;
-        }
-        // Add some padding to the progress, such we don't change the depth on the last frames of
-        // the animation. It's possible that a user flinging the feed quickly would scroll
-        // horizontally by accident, causing the device to enter client composition unnecessarily.
-        progress = Math.min(progress * 1.1f, 1f);
-
-        // Round out the progress to dedupe frequent, non-perceptable updates
-        int progressI = (int) (progress * 256);
-        float progressF = Utilities.boundToRange(progressI / 256f, 0f, 1f);
-        if (Float.compare(mOverlayScrollProgress, progressF) == 0) {
-            return;
-        }
-        mOverlayScrollProgress = progressF;
-        dispatchTransactionSurface(mDepth);
-    }
-
-    private boolean dispatchTransactionSurface(float depth) {
-        boolean supportsBlur = BlurUtils.supportsBlursOnWindows();
-        if (supportsBlur && (mSurface == null || !mSurface.isValid())) {
-            return false;
-        }
+    @Override
+    protected void applyDepthAndBlur() {
         ensureDependencies();
-        depth = Math.max(depth, mOverlayScrollProgress);
-        IBinder windowToken = mLauncher.getRootView().getWindowToken();
-        if (windowToken != null) {
-            mWallpaperManager.setWallpaperZoomOut(windowToken, depth);
-        }
-
-        if (supportsBlur) {
-            boolean hasOpaqueBg = mLauncher.getScrimView().isFullyOpaque();
-            boolean isSurfaceOpaque = !mHasContentBehindLauncher && hasOpaqueBg;
-
-            mCurrentBlur = !mCrossWindowBlursEnabled || mBlurDisabledForAppLaunch || hasOpaqueBg
-                    ? 0 : (int) (depth * mMaxBlurRadius);
-            SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()
-                    .setBackgroundBlurRadius(mSurface, mCurrentBlur)
-                    .setOpaque(mSurface, isSurfaceOpaque);
-
-            // Set early wake-up flags when we know we're executing an expensive operation, this way
-            // SurfaceFlinger will adjust its internal offsets to avoid jank.
-            boolean wantsEarlyWakeUp = depth > 0 && depth < 1;
-            if (wantsEarlyWakeUp && !mInEarlyWakeUp) {
-                transaction.setEarlyWakeupStart();
-                mInEarlyWakeUp = true;
-            } else if (!wantsEarlyWakeUp && mInEarlyWakeUp) {
-                transaction.setEarlyWakeupEnd();
-                mInEarlyWakeUp = false;
-            }
-
-            AttachedSurfaceControl rootSurfaceControl =
-                    mLauncher.getRootView().getRootSurfaceControl();
-            if (rootSurfaceControl != null) {
-                rootSurfaceControl.applyTransactionOnDraw(transaction);
-            }
-        }
-        return true;
+        super.applyDepthAndBlur();
     }
 
     @Override
     public void onMultiWindowModeChanged(boolean isInMultiWindowMode) {
         mIgnoreStateChangesDuringMultiWindowAnimation = true;
 
-        ObjectAnimator mwAnimation = ObjectAnimator.ofFloat(this, DEPTH,
+        ObjectAnimator mwAnimation = ObjectAnimator.ofFloat(this, STATE_DEPTH,
                 mLauncher.getStateManager().getState().getDepth(mLauncher, isInMultiWindowMode))
                 .setDuration(300);
         mwAnimation.addListener(new AnimatorListenerAdapter() {
@@ -377,7 +198,6 @@
         writer.println(prefix + "\tmMaxBlurRadius=" + mMaxBlurRadius);
         writer.println(prefix + "\tmCrossWindowBlursEnabled=" + mCrossWindowBlursEnabled);
         writer.println(prefix + "\tmSurface=" + mSurface);
-        writer.println(prefix + "\tmOverlayScrollProgress=" + mOverlayScrollProgress);
         writer.println(prefix + "\tmDepth=" + mDepth);
         writer.println(prefix + "\tmCurrentBlur=" + mCurrentBlur);
         writer.println(prefix + "\tmBlurDisabledForAppLaunch=" + mBlurDisabledForAppLaunch);
diff --git a/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarRecentAppsController.java b/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarRecentAppsController.java
new file mode 100644
index 0000000..acfbea3
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarRecentAppsController.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.util.SparseArray;
+
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.quickstep.RecentsModel;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Provides recent apps functionality specifically in a desktop environment.
+ */
+public class DesktopTaskbarRecentAppsController extends TaskbarRecentAppsController {
+
+    private final TaskbarActivityContext mContext;
+    private ArrayList<ItemInfo> mRunningApps = new ArrayList<>();
+    private AppInfo[] mApps;
+
+    public DesktopTaskbarRecentAppsController(TaskbarActivityContext context) {
+        mContext = context;
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        mApps = null;
+    }
+
+    @Override
+    protected void setApps(AppInfo[] apps) {
+        mApps = apps;
+    }
+
+    @Override
+    protected boolean isEnabled() {
+        return true;
+    }
+
+    /**
+     * Set mRunningApps to hold currently running applications using the list of currently running
+     * tasks. Filtering is also done to ignore applications that are already on the taskbar in the
+     * original hotseat.
+     */
+    @Override
+    protected void updateRunningApps(SparseArray<ItemInfo> hotseatItems) {
+        ArrayList<AppInfo> runningApps = getRunningAppsFromTasks();
+        ArrayList<ItemInfo> filteredRunningApps = new ArrayList<>();
+        for (AppInfo runningApp : runningApps) {
+            boolean shouldAddOnTaskbar = true;
+            for (int i = 0; i < hotseatItems.size(); i++) {
+                if (hotseatItems.keyAt(i) >= mControllers.taskbarActivityContext.getDeviceProfile()
+                        .numShownHotseatIcons) {
+                    break;
+                }
+                if (hotseatItems.valueAt(i).getTargetPackage()
+                        .equals(runningApp.getTargetPackage())) {
+                    shouldAddOnTaskbar = false;
+                    break;
+                }
+            }
+            if (shouldAddOnTaskbar) {
+                filteredRunningApps.add(new WorkspaceItemInfo(runningApp));
+            }
+        }
+        mRunningApps = filteredRunningApps;
+        mControllers.taskbarViewController.commitRunningAppsToUI();
+    }
+
+    /**
+     * Returns a copy of hotseatItems with the addition of currently running applications.
+     */
+    @Override
+    protected ItemInfo[] updateHotseatItemInfos(ItemInfo[] hotseatItemInfos) {
+        // hotseatItemInfos.length would be 0 if deviceProfile.numShownHotseatIcons is 0, so we
+        // don't want to show anything in the hotseat
+        if (hotseatItemInfos.length == 0) return hotseatItemInfos;
+
+        int runningAppsIndex = 0;
+        ItemInfo[] newHotseatItemsInfo = Arrays.copyOf(
+                hotseatItemInfos, hotseatItemInfos.length + mRunningApps.size());
+        for (int i = hotseatItemInfos.length; i < newHotseatItemsInfo.length; i++) {
+            newHotseatItemsInfo[i] = mRunningApps.get(runningAppsIndex);
+            runningAppsIndex++;
+        }
+        return newHotseatItemsInfo;
+    }
+
+
+    /**
+     * Returns a list of running applications from the list of currently running tasks.
+     */
+    private ArrayList<AppInfo> getRunningAppsFromTasks() {
+        ArrayList<ActivityManager.RunningTaskInfo> tasks =
+                RecentsModel.INSTANCE.get(mContext).getRunningTasks();
+        ArrayList<AppInfo> runningApps = new ArrayList<>();
+        // early return if apps is empty, since we would have no AppInfo to compare
+        if (mApps == null)  {
+            return runningApps;
+        }
+
+        Set<String> seenPackages = new HashSet<>();
+        for (ActivityManager.RunningTaskInfo taskInfo : tasks) {
+            if (taskInfo.realActivity == null) continue;
+
+            // If a different task for the same package has already been handled, skip this one
+            String taskPackage = taskInfo.realActivity.getPackageName();
+            if (seenPackages.contains(taskPackage)) continue;
+
+            // Otherwise, get the corresponding AppInfo and add it to the list
+            seenPackages.add(taskPackage);
+            AppInfo app = getAppInfo(taskInfo.realActivity);
+            if (app == null) continue;
+            runningApps.add(app);
+        }
+        return runningApps;
+    }
+
+    /**
+     * Retrieves the corresponding AppInfo for the activity.
+     */
+    private AppInfo getAppInfo(ComponentName activity) {
+        String packageName = activity.getPackageName();
+        for (AppInfo app : mApps) {
+            if (!packageName.equals(app.getTargetPackage())) {
+                continue;
+            }
+            return app;
+        }
+        return null;
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarUIController.java
index e2359c0..9393b0f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarUIController.java
@@ -16,7 +16,7 @@
 
 package com.android.launcher3.taskbar;
 
-import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
 
 /**
  * A data source which integrates with a Launcher instance, used specifically for a
@@ -24,24 +24,27 @@
  */
 public class DesktopTaskbarUIController extends TaskbarUIController {
 
-    private final BaseQuickstepLauncher mLauncher;
+    private final QuickstepLauncher mLauncher;
 
-    public DesktopTaskbarUIController(BaseQuickstepLauncher launcher) {
+    public DesktopTaskbarUIController(QuickstepLauncher launcher) {
         mLauncher = launcher;
     }
 
     @Override
     protected void init(TaskbarControllers taskbarControllers) {
+        super.init(taskbarControllers);
         mLauncher.getHotseat().setIconsAlpha(0f);
+        mControllers.taskbarViewController.updateRunningApps();
     }
 
     @Override
     protected void onDestroy() {
+        super.onDestroy();
         mLauncher.getHotseat().setIconsAlpha(1f);
     }
 
-    @Override
     /** Disable taskbar stashing in desktop environment. */
+    @Override
     public boolean supportsVisualStashing() {
         return false;
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/FloatingTaskIntentResolver.java b/quickstep/src/com/android/launcher3/taskbar/FloatingTaskIntentResolver.java
new file mode 100644
index 0000000..5f4d239
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/FloatingTaskIntentResolver.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar;
+
+import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.launcher3.R;
+
+// TODO: This would be replaced by the thing that has the role and provides the intent.
+/**
+ * Helper to determine what intent should be used to display in a floating window, if one
+ * exists.
+ */
+public class FloatingTaskIntentResolver {
+    private static final String TAG = FloatingTaskIntentResolver.class.getSimpleName();
+
+    @Nullable
+    /** Gets an intent for a floating task, if one exists. */
+    public static Intent getIntent(Context context) {
+        PackageManager pm = context.getPackageManager();
+        String pkg = context.getString(R.string.floating_task_package);
+        String action = context.getString(R.string.floating_task_action);
+        if (TextUtils.isEmpty(pkg) || TextUtils.isEmpty(action)) {
+            Log.d(TAG, "intent could not be found, pkg= " + pkg + " action= " + action);
+            return null;
+        }
+        Intent intent = createIntent(pm, null, pkg, action);
+        if (intent != null) {
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            return intent;
+        }
+        Log.d(TAG, "No valid intent found!");
+        return null;
+    }
+
+    @Nullable
+    private static Intent createIntent(PackageManager pm, @Nullable String activityName,
+            String packageName, String action) {
+        if (TextUtils.isEmpty(activityName)) {
+            activityName = queryActivityForAction(pm, packageName, action);
+        }
+        if (TextUtils.isEmpty(activityName)) {
+            Log.d(TAG, "Activity name is empty even after action search: " + action);
+            return null;
+        }
+        ComponentName component = new ComponentName(packageName, activityName);
+        Intent intent = new Intent(action).setComponent(component).setPackage(packageName);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        Log.d(TAG, "createIntent returning: " + intent);
+        return intent;
+    }
+
+    @Nullable
+    private static String queryActivityForAction(PackageManager pm, String packageName,
+            String action) {
+        Intent intent = new Intent(action).setPackage(packageName);
+        ResolveInfo resolveInfo = pm.resolveActivity(intent, MATCH_DEFAULT_ONLY);
+        if (resolveInfo == null || resolveInfo.activityInfo == null) {
+            Log.d(TAG, "queryActivityForAction: + " + resolveInfo);
+            return null;
+        }
+        ActivityInfo info = resolveInfo.activityInfo;
+        if (!info.exported) {
+            Log.d(TAG, "queryActivityForAction: + " + info + " not exported");
+            return null;
+        }
+        if (!info.enabled) {
+            Log.d(TAG, "queryActivityForAction: + " + info + " not enabled");
+            return null;
+        }
+        return resolveInfo.activityInfo.name;
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/LaunchFloatingTaskButton.java b/quickstep/src/com/android/launcher3/taskbar/LaunchFloatingTaskButton.java
new file mode 100644
index 0000000..b15669b
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/LaunchFloatingTaskButton.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.util.AttributeSet;
+import android.view.ContextThemeWrapper;
+
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.R;
+import com.android.launcher3.icons.FastBitmapDrawable;
+
+/**
+ * Button in Taskbar that opens something in a floating task.
+ */
+public class LaunchFloatingTaskButton extends BubbleTextView {
+
+    public LaunchFloatingTaskButton(Context context) {
+        this(context, null);
+    }
+
+    public LaunchFloatingTaskButton(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public LaunchFloatingTaskButton(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        Context theme = new ContextThemeWrapper(context, R.style.AllAppsButtonTheme);
+        Bitmap bitmap = LauncherAppState.getInstance(context).getIconCache().getIconFactory()
+                .createScaledBitmapWithShadow(
+                        theme.getDrawable(R.drawable.ic_floating_task_button));
+        setIcon(new FastBitmapDrawable(bitmap));
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index ca30e72..a219ac6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -15,8 +15,10 @@
  */
 package com.android.launcher3.taskbar;
 
+import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
+
 import static com.android.launcher3.taskbar.TaskbarLauncherStateController.FLAG_RESUMED;
-import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_EXTRA_NAVIGATION_BAR;
+import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
@@ -30,9 +32,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
 
-import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.QuickstepTransitionManager;
@@ -42,6 +42,7 @@
 import com.android.launcher3.logging.InstanceIdSequence;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.util.OnboardingPrefs;
 import com.android.quickstep.AnimatedFloat;
 import com.android.quickstep.RecentsAnimationCallbacks;
@@ -65,7 +66,7 @@
 
     private final SparseArray<Float> mTaskbarInAppDisplayProgress = new SparseArray<>(4);
 
-    private final BaseQuickstepLauncher mLauncher;
+    private final QuickstepLauncher mLauncher;
 
     private final DeviceProfile.OnDeviceProfileChangeListener mOnDeviceProfileChangeListener =
             dp -> {
@@ -81,7 +82,7 @@
     private final TaskbarLauncherStateController
             mTaskbarLauncherStateController = new TaskbarLauncherStateController();
 
-    public LauncherTaskbarUIController(BaseQuickstepLauncher launcher) {
+    public LauncherTaskbarUIController(QuickstepLauncher launcher) {
         mLauncher = launcher;
     }
 
@@ -124,24 +125,6 @@
     }
 
     /**
-     * Enables manual taskbar stashing. This method should only be used for tests that need to
-     * stash/unstash the taskbar.
-     */
-    @VisibleForTesting
-    public void enableManualStashingForTests(boolean enableManualStashing) {
-        mControllers.taskbarStashController.enableManualStashingForTests(enableManualStashing);
-    }
-
-    /**
-     * Unstashes the Taskbar if it is stashed. This method should only be used to unstash the
-     * taskbar at the end of a test.
-     */
-    @VisibleForTesting
-    public void unstashTaskbarIfStashed() {
-        mControllers.taskbarStashController.onLongPressToUnstashTaskbar();
-    }
-
-    /**
      * Adds the Launcher resume animator to the given animator set.
      *
      * This should be used to run a Launcher resume animation whose progress matches a
@@ -171,7 +154,9 @@
                 isResumed,
                 fromInit,
                 /* startAnimation= */ true,
-                QuickstepTransitionManager.CONTENT_ALPHA_DURATION);
+                !isResumed
+                        ? QuickstepTransitionManager.TASKBAR_TO_APP_DURATION
+                        : QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION);
     }
 
     @Nullable
@@ -186,6 +171,13 @@
             }
         }
 
+        if (ENABLE_SHELL_TRANSITIONS
+                && !mLauncher.getStateManager().getState().isTaskbarAlignedWithHotseat(mLauncher)) {
+            // Launcher is resumed, but in a state where taskbar is still independent, so
+            // ignore the state change.
+            return null;
+        }
+
         mTaskbarLauncherStateController.updateStateForFlag(FLAG_RESUMED, isResumed);
         return mTaskbarLauncherStateController.applyState(fromInit ? 0 : duration, startAnimation);
     }
@@ -352,6 +344,13 @@
     }
 
     @Override
+    public void onExpandPip() {
+        super.onExpandPip();
+        mTaskbarLauncherStateController.updateStateForFlag(FLAG_RESUMED, false);
+        mTaskbarLauncherStateController.applyState();
+    }
+
+    @Override
     public void dumpLogs(String prefix, PrintWriter pw) {
         super.dumpLogs(prefix, pw);
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 5d576f7..a90af04 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -15,15 +15,22 @@
  */
 package com.android.launcher3.taskbar;
 
+import static android.view.View.AccessibilityDelegate;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
+
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
 import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
 import static com.android.launcher3.taskbar.LauncherTaskbarUIController.SYSUI_SURFACE_PROGRESS_INDEX;
+import static com.android.launcher3.taskbar.TaskbarManager.isPhoneButtonNavMode;
+import static com.android.launcher3.taskbar.TaskbarManager.isPhoneMode;
 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y;
 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK;
 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME;
 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_IME_SWITCH;
 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_RECENTS;
 import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_KEYGUARD;
+import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_SMALL_SCREEN;
 import static com.android.launcher3.taskbar.Utilities.appendFlag;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
@@ -35,7 +42,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.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_REGION;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
 
 import android.animation.ArgbEvaluator;
 import android.animation.ObjectAnimator;
@@ -45,6 +52,7 @@
 import android.content.pm.ActivityInfo.Config;
 import android.content.res.ColorStateList;
 import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.Region;
@@ -61,11 +69,13 @@
 import android.view.View.OnClickListener;
 import android.view.View.OnHoverListener;
 import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
 import android.view.WindowManager;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
@@ -79,7 +89,6 @@
 import com.android.systemui.shared.rotation.RotationButton;
 import com.android.systemui.shared.rotation.RotationButtonController;
 import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.shared.system.ViewTreeObserverWrapper;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -93,7 +102,7 @@
 
     private final Rect mTempRect = new Rect();
 
-    private static final int FLAG_SWITCHER_SUPPORTED = 1 << 0;
+    private static final int FLAG_SWITCHER_SHOWING = 1 << 0;
     private static final int FLAG_IME_VISIBLE = 1 << 1;
     private static final int FLAG_ROTATION_BUTTON_VISIBLE = 1 << 2;
     private static final int FLAG_A11Y_VISIBLE = 1 << 3;
@@ -105,8 +114,8 @@
     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 int FLAG_SMALL_SCREEN = 1 << 13;
 
     private static final String NAV_BUTTONS_SEPARATE_WINDOW_TITLE = "Taskbar Nav Buttons";
 
@@ -120,7 +129,7 @@
 
     private final TaskbarActivityContext mContext;
     private final FrameLayout mNavButtonsView;
-    private final ViewGroup mNavButtonContainer;
+    private final LinearLayout mNavButtonContainer;
     // Used for IME+A11Y buttons
     private final ViewGroup mEndContextualContainer;
     private final ViewGroup mStartContextualContainer;
@@ -158,7 +167,7 @@
     // Variables for moving nav buttons to a separate window above IME
     private boolean mAreNavButtonsInSeparateWindow = false;
     private BaseDragLayer<TaskbarActivityContext> mSeparateWindowParent; // Initialized in init.
-    private final ViewTreeObserverWrapper.OnComputeInsetsListener mSeparateWindowInsetsComputer =
+    private final ViewTreeObserver.OnComputeInternalInsetsListener mSeparateWindowInsetsComputer =
             this::onComputeInsetsForSeparateWindow;
     private final RecentsHitboxExtender mHitboxExtender = new RecentsHitboxExtender();
 
@@ -178,9 +187,13 @@
      */
     public void init(TaskbarControllers controllers) {
         mControllers = controllers;
-        mNavButtonsView.getLayoutParams().height = mContext.getDeviceProfile().taskbarSize;
-
         boolean isThreeButtonNav = mContext.isThreeButtonNav();
+        DeviceProfile deviceProfile = mContext.getDeviceProfile();
+        Resources resources = mContext.getResources();
+        mNavButtonsView.getLayoutParams().height = !isPhoneMode(deviceProfile) ?
+                deviceProfile.taskbarSize :
+                resources.getDimensionPixelSize(R.dimen.taskbar_size);
+
         mIsImeRenderingNavButtons =
                 InputMethodService.canImeRenderGesturalNavButtons() && mContext.imeDrawsImeNavBar();
         if (!mIsImeRenderingNavButtons) {
@@ -189,7 +202,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_SHOWING) != 0)
                             && ((flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0)));
         }
 
@@ -199,6 +212,11 @@
                 flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0
                         && (flags & FLAG_SCREEN_PINNING_ACTIVE) == 0));
 
+        mPropertyHolders.add(new StatePropertyHolder(
+                mControllers.taskbarViewController.getTaskbarIconAlpha()
+                        .getProperty(ALPHA_INDEX_SMALL_SCREEN),
+                flags -> (flags & FLAG_SMALL_SCREEN) == 0));
+
         mPropertyHolders.add(new StatePropertyHolder(mControllers.taskbarDragLayerController
                 .getKeyguardBgTaskbar(), flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0));
 
@@ -207,9 +225,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));
@@ -225,7 +246,8 @@
         if (alwaysShowButtons) {
             initButtons(mNavButtonContainer, mEndContextualContainer,
                     mControllers.navButtonController);
-
+            updateButtonLayoutSpacing();
+            updateStateForFlag(FLAG_SMALL_SCREEN, isPhoneButtonNavMode(mContext));
             if (isInSetup) {
                 // Since setup wizard only has back button enabled, it looks strange to be
                 // end-aligned, so start-align instead.
@@ -238,18 +260,18 @@
 
                 // TODO(b/210906568) Dark intensity is currently not propagated during setup, so set
                 //  it based on dark theme for now.
-                int mode = mContext.getResources().getConfiguration().uiMode
+                int mode = resources.getConfiguration().uiMode
                         & Configuration.UI_MODE_NIGHT_MASK;
                 boolean isDarkTheme = mode == Configuration.UI_MODE_NIGHT_YES;
                 mTaskbarNavButtonDarkIntensity.updateValue(isDarkTheme ? 0 : 1);
             } else if (isInKidsMode) {
-                int iconSize = mContext.getResources().getDimensionPixelSize(
+                int iconSize = resources.getDimensionPixelSize(
                         R.dimen.taskbar_icon_size_kids);
-                int buttonWidth = mContext.getResources().getDimensionPixelSize(
+                int buttonWidth = resources.getDimensionPixelSize(
                         R.dimen.taskbar_nav_buttons_width_kids);
-                int buttonHeight = mContext.getResources().getDimensionPixelSize(
+                int buttonHeight = resources.getDimensionPixelSize(
                         R.dimen.taskbar_nav_buttons_height_kids);
-                int buttonRadius = mContext.getResources().getDimensionPixelSize(
+                int buttonRadius = resources.getDimensionPixelSize(
                         R.dimen.taskbar_nav_buttons_corner_radius_kids);
                 int paddingleft = (buttonWidth - iconSize) / 2;
                 int paddingRight = paddingleft;
@@ -271,7 +293,7 @@
                         buttonWidth,
                         buttonHeight
                 );
-                int homeButtonLeftMargin = mContext.getResources().getDimensionPixelSize(
+                int homeButtonLeftMargin = resources.getDimensionPixelSize(
                         R.dimen.taskbar_home_button_left_margin_kids);
                 homeLayoutparams.setMargins(homeButtonLeftMargin, 0, 0, 0);
                 mHomeButton.setLayoutParams(homeLayoutparams);
@@ -281,7 +303,7 @@
                         buttonWidth,
                         buttonHeight
                 );
-                int backButtonLeftMargin = mContext.getResources().getDimensionPixelSize(
+                int backButtonLeftMargin = resources.getDimensionPixelSize(
                         R.dimen.taskbar_back_button_left_margin_kids);
                 backLayoutParams.setMargins(backButtonLeftMargin, 0, 0, 0);
                 mBackButton.setLayoutParams(backLayoutParams);
@@ -336,7 +358,7 @@
             if (!mIsImeRenderingNavButtons) {
                 View imeDownButton = addButton(R.drawable.ic_sysbar_back, BUTTON_BACK,
                         mStartContextualContainer, mControllers.navButtonController, R.id.back);
-                imeDownButton.setRotation(Utilities.isRtl(mContext.getResources()) ? 90 : -90);
+                imeDownButton.setRotation(Utilities.isRtl(resources) ? 90 : -90);
                 // Only show when IME is visible.
                 mPropertyHolders.add(new StatePropertyHolder(imeDownButton,
                         flags -> (flags & FLAG_IME_VISIBLE) != 0));
@@ -415,7 +437,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,22 +465,26 @@
                 | 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);
-        updateStateForFlag(FLAG_SWITCHER_SUPPORTED, isImeSwitcherShowing);
+        updateStateForFlag(FLAG_SWITCHER_SHOWING, isImeSwitcherShowing);
         updateStateForFlag(FLAG_A11Y_VISIBLE, a11yVisible);
         updateStateForFlag(FLAG_DISABLE_HOME, isHomeDisabled);
         updateStateForFlag(FLAG_DISABLE_RECENTS, isRecentsDisabled);
         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
             boolean a11yLongClickable =
                     (sysUiStateFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
             mA11yButton.setLongClickable(a11yLongClickable);
+            updateButtonLayoutSpacing();
         }
     }
 
@@ -474,6 +500,13 @@
     }
 
     /**
+     * @return {@code true} if A11y is showing in 3 button nav taskbar
+     */
+    private boolean isContextualButtonShowing() {
+        return mContext.isThreeButtonNav() && (mState & FLAG_A11Y_VISIBLE) != 0;
+    }
+
+    /**
      * Should be called when we need to show back button for bouncer
      */
     public void setBackForBouncer(boolean isBouncerVisible) {
@@ -499,6 +532,13 @@
     }
 
     /**
+     * Returns true if IME switcher is visible
+     */
+    public boolean isImeSwitcherVisible() {
+        return (mState & FLAG_SWITCHER_SHOWING) != 0;
+    }
+
+    /**
      * Returns true if the home button is disabled
      */
     public boolean isHomeDisabled() {
@@ -543,6 +583,26 @@
         return mHomeButtonAlpha;
     }
 
+    /**
+     * Sets the AccessibilityDelegate for the home button.
+     */
+    public void setHomeButtonAccessibilityDelegate(AccessibilityDelegate accessibilityDelegate) {
+        if (mHomeButton == null) {
+            return;
+        }
+        mHomeButton.setAccessibilityDelegate(accessibilityDelegate);
+    }
+
+    /**
+     * Sets the AccessibilityDelegate for the back button.
+     */
+    public void setBackButtonAccessibilityDelegate(AccessibilityDelegate accessibilityDelegate) {
+        if (mBackButton == null) {
+            return;
+        }
+        mBackButton.setAccessibilityDelegate(accessibilityDelegate);
+    }
+
     /** Use to set the translationY for the all nav+contextual buttons */
     public AnimatedFloat getTaskbarNavButtonTranslationY() {
         return mTaskbarNavButtonTranslationY;
@@ -590,6 +650,9 @@
     }
 
     private void updateNavButtonTranslationY() {
+        if (isPhoneButtonNavMode(mContext)) {
+            return;
+        }
         final float normalTranslationY = mTaskbarNavButtonTranslationY.value;
         final float imeAdjustmentTranslationY = mTaskbarNavButtonTranslationYForIme.value;
         TaskbarUIController uiController = mControllers.uiController;
@@ -626,9 +689,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;
     }
 
@@ -649,6 +712,91 @@
         if (mFloatingRotationButton != null) {
             mFloatingRotationButton.onConfigurationChanged(configChanges);
         }
+        updateButtonLayoutSpacing();
+    }
+
+    /**
+     * Adds the correct spacing to 3 button nav container. No-op if using gesture nav or kids mode.
+     */
+    private void updateButtonLayoutSpacing() {
+        if (!mContext.isThreeButtonNav() || mContext.isNavBarKidsModeActive()) {
+            return;
+        }
+
+        if (isPhoneButtonNavMode(mContext)) {
+            updatePhoneButtonSpacing();
+            return;
+        }
+
+        DeviceProfile dp = mContext.getDeviceProfile();
+        Resources res = mContext.getResources();
+
+        // Add spacing after the end of the last nav button
+        FrameLayout.LayoutParams navButtonParams =
+                (FrameLayout.LayoutParams) mNavButtonContainer.getLayoutParams();
+        navButtonParams.gravity = Gravity.END;
+        navButtonParams.width = FrameLayout.LayoutParams.WRAP_CONTENT;
+        navButtonParams.height = MATCH_PARENT;
+
+        int navMarginEnd = (int) res.getDimension(dp.inv.inlineNavButtonsEndSpacing);
+        int contextualWidth = mEndContextualContainer.getWidth();
+        // If contextual buttons are showing, we check if the end margin is enough for the
+        // contextual button to be showing - if not, move the nav buttons over a smidge
+        if (isContextualButtonShowing() && navMarginEnd < contextualWidth) {
+            // Additional spacing, eat up half of space between last icon and nav button
+            navMarginEnd += res.getDimensionPixelSize(R.dimen.taskbar_hotseat_nav_spacing) / 2;
+        }
+        navButtonParams.setMarginEnd(navMarginEnd);
+        mNavButtonContainer.setLayoutParams(navButtonParams);
+
+        // Add the spaces in between the nav buttons
+        int spaceInBetween = res.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween);
+        for (int i = 0; i < mNavButtonContainer.getChildCount(); i++) {
+            View navButton = mNavButtonContainer.getChildAt(i);
+            LinearLayout.LayoutParams buttonLayoutParams =
+                    (LinearLayout.LayoutParams) navButton.getLayoutParams();
+            buttonLayoutParams.weight = 0;
+            if (i == 0) {
+                buttonLayoutParams.setMarginEnd(spaceInBetween / 2);
+            } else if (i == mNavButtonContainer.getChildCount() - 1) {
+                buttonLayoutParams.setMarginStart(spaceInBetween / 2);
+            } else {
+                buttonLayoutParams.setMarginStart(spaceInBetween / 2);
+                buttonLayoutParams.setMarginEnd(spaceInBetween / 2);
+            }
+        }
+    }
+
+    /** Uniformly spaces out the 3 button nav for smaller phone screens */
+    private void updatePhoneButtonSpacing() {
+        DeviceProfile dp = mContext.getDeviceProfile();
+        Resources res = mContext.getResources();
+
+        // TODO: Polish pending, this is just to make it usable
+        FrameLayout.LayoutParams navContainerParams =
+                (FrameLayout.LayoutParams) mNavButtonContainer.getLayoutParams();
+        int endStartMargins = res.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size);
+        navContainerParams.gravity = Gravity.CENTER;
+        navContainerParams.setMarginEnd(endStartMargins);
+        navContainerParams.setMarginStart(endStartMargins);
+        mNavButtonContainer.setLayoutParams(navContainerParams);
+
+        // Add the spaces in between the nav buttons
+        int spaceInBetween = res.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween_phone);
+        for (int i = 0; i < mNavButtonContainer.getChildCount(); i++) {
+            View navButton = mNavButtonContainer.getChildAt(i);
+            LinearLayout.LayoutParams buttonLayoutParams =
+                    (LinearLayout.LayoutParams) navButton.getLayoutParams();
+            buttonLayoutParams.weight = 1;
+            if (i == 0) {
+                buttonLayoutParams.setMarginEnd(spaceInBetween / 2);
+            } else if (i == mNavButtonContainer.getChildCount() - 1) {
+                buttonLayoutParams.setMarginStart(spaceInBetween / 2);
+            } else {
+                buttonLayoutParams.setMarginStart(spaceInBetween / 2);
+                buttonLayoutParams.setMarginEnd(spaceInBetween / 2);
+            }
+        }
     }
 
     public void onDestroy() {
@@ -659,6 +807,8 @@
         }
 
         moveNavButtonsBackToTaskbarWindow();
+        mNavButtonContainer.removeAllViews();
+        mAllButtons.clear();
     }
 
     /**
@@ -677,14 +827,14 @@
         mSeparateWindowParent.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
             @Override
             public void onViewAttachedToWindow(View view) {
-                ViewTreeObserverWrapper.addOnComputeInsetsListener(
-                        mSeparateWindowParent.getViewTreeObserver(), mSeparateWindowInsetsComputer);
+                mSeparateWindowParent.getViewTreeObserver().addOnComputeInternalInsetsListener(
+                        mSeparateWindowInsetsComputer);
             }
 
             @Override
             public void onViewDetachedFromWindow(View view) {
                 mSeparateWindowParent.removeOnAttachStateChangeListener(this);
-                ViewTreeObserverWrapper.removeOnComputeInsetsListener(
+                mSeparateWindowParent.getViewTreeObserver().removeOnComputeInternalInsetsListener(
                         mSeparateWindowInsetsComputer);
             }
         });
@@ -712,7 +862,7 @@
         mContext.getDragLayer().addView(mNavButtonsView);
     }
 
-    private void onComputeInsetsForSeparateWindow(ViewTreeObserverWrapper.InsetsInfo insetsInfo) {
+    private void onComputeInsetsForSeparateWindow(ViewTreeObserver.InternalInsetsInfo insetsInfo) {
         addVisibleButtonsRegion(mSeparateWindowParent, insetsInfo.touchableRegion);
         insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
     }
@@ -721,22 +871,17 @@
     public void dumpLogs(String prefix, PrintWriter pw) {
         pw.println(prefix + "NavbarButtonsViewController:");
 
-        pw.println(String.format("%s\tmState=%s", prefix, getStateString(mState)));
-        pw.println(String.format(
-                "%s\tmLightIconColor=0x%s", prefix, Integer.toHexString(mLightIconColor)));
-        pw.println(String.format(
-                "%s\tmDarkIconColor=0x%s", prefix, Integer.toHexString(mDarkIconColor)));
-        pw.println(String.format(
-                "%s\tmFloatingRotationButtonBounds=%s", prefix, mFloatingRotationButtonBounds));
-        pw.println(String.format(
-                "%s\tmSysuiStateFlags=%s",
-                prefix,
-                QuickStepContract.getSystemUiStateString(mSysuiStateFlags)));
+        pw.println(prefix + "\tmState=" + getStateString(mState));
+        pw.println(prefix + "\tmLightIconColor=" + Integer.toHexString(mLightIconColor));
+        pw.println(prefix + "\tmDarkIconColor=" + Integer.toHexString(mDarkIconColor));
+        pw.println(prefix + "\tmFloatingRotationButtonBounds=" + mFloatingRotationButtonBounds);
+        pw.println(prefix + "\tmSysuiStateFlags=" + QuickStepContract.getSystemUiStateString(
+                mSysuiStateFlags));
     }
 
     private static String getStateString(int flags) {
         StringJoiner str = new StringJoiner("|");
-        appendFlag(str, flags, FLAG_SWITCHER_SUPPORTED, "FLAG_SWITCHER_SUPPORTED");
+        appendFlag(str, flags, FLAG_SWITCHER_SHOWING, "FLAG_SWITCHER_SHOWING");
         appendFlag(str, flags, FLAG_IME_VISIBLE, "FLAG_IME_VISIBLE");
         appendFlag(str, flags, FLAG_ROTATION_BUTTON_VISIBLE, "FLAG_ROTATION_BUTTON_VISIBLE");
         appendFlag(str, flags, FLAG_A11Y_VISIBLE, "FLAG_A11Y_VISIBLE");
@@ -750,6 +895,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..6b67b50 100644
--- a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
@@ -25,6 +25,7 @@
 import android.view.View;
 import android.view.ViewOutlineProvider;
 
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.RevealOutlineAnimation;
@@ -43,7 +44,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.
@@ -54,9 +56,9 @@
     private final TaskbarActivityContext mActivity;
     private final SharedPreferences mPrefs;
     private final StashedHandleView mStashedHandleView;
-    private final int mStashedHandleWidth;
+    private int mStashedHandleWidth;
     private final int mStashedHandleHeight;
-    private final RegionSamplingHelper mRegionSamplingHelper;
+    private RegionSamplingHelper mRegionSamplingHelper;
     private final MultiValueAlpha mTaskbarStashedHandleAlpha;
     private final AnimatedFloat mTaskbarStashedHandleHintScale = new AnimatedFloat(
             this::updateStashedHandleHintScale);
@@ -84,30 +86,27 @@
                 mPrefs.getBoolean(SHARED_PREFS_STASHED_HANDLE_REGION_DARK_KEY, false),
                 false /* animate */);
         final Resources resources = mActivity.getResources();
-        mStashedHandleWidth = resources.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_width);
         mStashedHandleHeight = resources.getDimensionPixelSize(
                 R.dimen.taskbar_stashed_handle_height);
-        mRegionSamplingHelper = new RegionSamplingHelper(mStashedHandleView,
-                new RegionSamplingHelper.SamplingCallback() {
-                    @Override
-                    public void onRegionDarknessChanged(boolean isRegionDark) {
-                        mStashedHandleView.updateHandleColor(isRegionDark, true /* animate */);
-                        mPrefs.edit().putBoolean(SHARED_PREFS_STASHED_HANDLE_REGION_DARK_KEY,
-                                isRegionDark).apply();
-                    }
-
-                    @Override
-                    public Rect getSampledRegion(View sampledView) {
-                        return mStashedHandleView.getSampledRegion();
-                    }
-                }, Executors.UI_HELPER_EXECUTOR);
     }
 
     public void init(TaskbarControllers controllers) {
         mControllers = controllers;
-        mStashedHandleView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarSize;
+        DeviceProfile deviceProfile = mActivity.getDeviceProfile();
+        Resources resources = mActivity.getResources();
+        if (isPhoneGestureNavMode(mActivity.getDeviceProfile())) {
+            mStashedHandleView.getLayoutParams().height =
+                    resources.getDimensionPixelSize(R.dimen.taskbar_size);
+            mStashedHandleWidth =
+                    resources.getDimensionPixelSize(R.dimen.taskbar_stashed_small_screen);
+        } else {
+            mStashedHandleView.getLayoutParams().height = deviceProfile.taskbarSize;
+            mStashedHandleWidth =
+                    resources.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_width);
+        }
 
-        mTaskbarStashedHandleAlpha.getProperty(ALPHA_INDEX_STASHED).setValue(0);
+        mTaskbarStashedHandleAlpha.getProperty(ALPHA_INDEX_STASHED).setValue(
+                isPhoneGestureNavMode(deviceProfile) ? 1 : 0);
         mTaskbarStashedHandleHintScale.updateValue(1f);
 
         final int stashedTaskbarHeight = mControllers.taskbarStashController.getStashedHeight();
@@ -134,10 +133,37 @@
             view.setPivotX(stashedCenterX);
             view.setPivotY(stashedCenterY);
         });
+        initRegionSampler();
+        if (isPhoneGestureNavMode(deviceProfile)) {
+            onIsStashedChanged(true);
+        }
     }
 
+    private void initRegionSampler() {
+        mRegionSamplingHelper = new RegionSamplingHelper(mStashedHandleView,
+                new RegionSamplingHelper.SamplingCallback() {
+                    @Override
+                    public void onRegionDarknessChanged(boolean isRegionDark) {
+                        mStashedHandleView.updateHandleColor(isRegionDark, true /* animate */);
+                        mPrefs.edit().putBoolean(SHARED_PREFS_STASHED_HANDLE_REGION_DARK_KEY,
+                                isRegionDark).apply();
+                    }
+
+                    @Override
+                    public Rect getSampledRegion(View sampledView) {
+                        return mStashedHandleView.getSampledRegion();
+                    }
+                }, Executors.UI_HELPER_EXECUTOR);
+    }
+
+
     public void onDestroy() {
         mRegionSamplingHelper.stopAndDestroy();
+        mRegionSamplingHelper = null;
+    }
+
+    private boolean isPhoneGestureNavMode(DeviceProfile deviceProfile) {
+        return TaskbarManager.isPhoneMode(deviceProfile) && !mActivity.isThreeButtonNav();
     }
 
     public MultiValueAlpha getStashedHandleAlpha() {
@@ -208,10 +234,9 @@
     public void dumpLogs(String prefix, PrintWriter pw) {
         pw.println(prefix + "StashedHandleViewController:");
 
-        pw.println(String.format(
-                "%s\tisStashedHandleVisible=%b", prefix, isStashedHandleVisible()));
-        pw.println(String.format("%s\tmStashedHandleWidth=%dpx", prefix, mStashedHandleWidth));
-        pw.println(String.format("%s\tmStashedHandleHeight=%dpx", prefix, mStashedHandleHeight));
+        pw.println(prefix + "\tisStashedHandleVisible=" + isStashedHandleVisible());
+        pw.println(prefix + "\tmStashedHandleWidth=" + mStashedHandleWidth);
+        pw.println(prefix + "\tmStashedHandleHeight=" + mStashedHandleHeight);
         mRegionSamplingHelper.dump(prefix, pw);
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index dc2e3b6..9d15ea8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -23,10 +23,12 @@
 
 import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
 import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
-import static com.android.launcher3.ResourceUtils.getBoolByName;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_OPEN;
+import static com.android.launcher3.taskbar.TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW;
+import static com.android.launcher3.testing.shared.ResourceUtils.getBoolByName;
 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;
@@ -75,10 +77,10 @@
 import com.android.launcher3.popup.PopupDataProvider;
 import com.android.launcher3.taskbar.allapps.TaskbarAllAppsController;
 import com.android.launcher3.testing.TestLogging;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.DisplayController.NavigationMode;
+import com.android.launcher3.util.NavigationMode;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.SettingsCache;
 import com.android.launcher3.util.TraceHelper;
@@ -117,7 +119,7 @@
     // The size we should return to when we call setTaskbarWindowFullscreen(false)
     private int mLastRequestedNonFullscreenHeight;
 
-    private final NavigationMode mNavMode;
+    private NavigationMode mNavMode;
     private final boolean mImeDrawsImeNavBar;
     private final ViewCache mViewCache = new ViewCache();
 
@@ -129,6 +131,8 @@
     // The flag to know if the window is excluded from magnification region computation.
     private boolean mIsExcludeFromMagnificationRegion = false;
     private boolean mBindingItems = false;
+    private boolean mAddedWindow = false;
+
 
     private final TaskbarShortcutMenuAccessibilityDelegate mAccessibilityDelegate;
 
@@ -172,13 +176,15 @@
 
         mAccessibilityDelegate = new TaskbarShortcutMenuAccessibilityDelegate(this);
 
+        final boolean isDesktopMode = getPackageManager().hasSystemFeature(FEATURE_PC);
+
         // Construct controllers.
         mControllers = new TaskbarControllers(this,
                 new TaskbarDragController(this),
                 buttonController,
-                getPackageManager().hasSystemFeature(FEATURE_PC)
-                        ? new DesktopNavbarButtonsViewController(this, navButtonsView) :
-                        new NavbarButtonsViewController(this, navButtonsView),
+                isDesktopMode
+                        ? new DesktopNavbarButtonsViewController(this, navButtonsView)
+                        : new NavbarButtonsViewController(this, navButtonsView),
                 new RotationButtonController(this,
                         c.getColor(R.color.taskbar_nav_icon_light_color),
                         c.getColor(R.color.taskbar_nav_icon_dark_color),
@@ -200,7 +206,11 @@
                 new TaskbarPopupController(this),
                 new TaskbarForceVisibleImmersiveController(this),
                 new TaskbarAllAppsController(this, dp),
-                new TaskbarInsetsController(this));
+                new TaskbarInsetsController(this),
+                new VoiceInteractionWindowController(this),
+                isDesktopMode
+                        ? new DesktopTaskbarRecentAppsController(this)
+                        : TaskbarRecentAppsController.DEFAULT);
     }
 
     public void init(@NonNull TaskbarSharedState sharedState) {
@@ -211,7 +221,12 @@
         mControllers.init(sharedState);
         updateSysuiStateFlags(sharedState.sysuiStateFlags, true /* fromInit */);
 
-        mWindowManager.addView(mDragLayer, mWindowLayoutParams);
+        if (!mAddedWindow) {
+            mWindowManager.addView(mDragLayer, mWindowLayoutParams);
+            mAddedWindow = true;
+        } else {
+            mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
+        }
     }
 
     @Override
@@ -220,7 +235,8 @@
     }
 
     /** Updates {@link DeviceProfile} instances for any Taskbar windows. */
-    public void updateDeviceProfile(DeviceProfile dp) {
+    public void updateDeviceProfile(DeviceProfile dp, NavigationMode navMode) {
+        mNavMode = navMode;
         mControllers.taskbarAllAppsController.updateDeviceProfile(dp);
         mDeviceProfile = dp.copy(this);
         updateIconSize(getResources());
@@ -246,14 +262,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();
@@ -445,7 +470,10 @@
         mIsDestroyed = true;
         setUIController(TaskbarUIController.DEFAULT);
         mControllers.onDestroy();
-        mWindowManager.removeViewImmediate(mDragLayer);
+        if (!FLAG_HIDE_NAVBAR_WINDOW) {
+            mWindowManager.removeViewImmediate(mDragLayer);
+            mAddedWindow = false;
+        }
     }
 
     public void updateSysuiStateFlags(int systemUiStateFlags, boolean fromInit) {
@@ -453,6 +481,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 +498,8 @@
                 fromInit);
         mControllers.navButtonController.updateSysuiFlags(systemUiStateFlags);
         mControllers.taskbarForceVisibleImmersiveController.updateSysuiFlags(systemUiStateFlags);
+        mControllers.voiceInteractionWindowController.setIsVoiceInteractionWindowVisible(
+                (systemUiStateFlags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0, fromInit);
     }
 
     /**
@@ -576,6 +608,12 @@
      * Returns the default height of the window, including the static corner radii above taskbar.
      */
     public int getDefaultTaskbarWindowHeight() {
+        if (FLAG_HIDE_NAVBAR_WINDOW && mDeviceProfile.isPhone) {
+            Resources resources = getResources();
+            return isThreeButtonNav() ?
+                    resources.getDimensionPixelSize(R.dimen.taskbar_size) :
+                    resources.getDimensionPixelSize(R.dimen.taskbar_stashed_size);
+        }
         return mDeviceProfile.taskbarSize + Math.max(getLeftCornerRadius(), getRightCornerRadius());
     }
 
@@ -607,12 +645,16 @@
 
     /** Adds the given view to WindowManager with the provided LayoutParams (creates new window). */
     public void addWindowView(View view, WindowManager.LayoutParams windowLayoutParams) {
-        mWindowManager.addView(view, windowLayoutParams);
+        if (!view.isAttachedToWindow()) {
+            mWindowManager.addView(view, windowLayoutParams);
+        }
     }
 
     /** 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) {
@@ -731,6 +773,24 @@
         mControllers.taskbarStashController.startUnstashHint(animateForward);
     }
 
+    /**
+     * Enables manual taskbar stashing. This method should only be used for tests that need to
+     * stash/unstash the taskbar.
+     */
+    @VisibleForTesting
+    public void enableManualStashingDuringTests(boolean enableManualStashing) {
+        mControllers.taskbarStashController.enableManualStashingDuringTests(enableManualStashing);
+    }
+
+    /**
+     * Unstashes the Taskbar if it is stashed. This method should only be used to unstash the
+     * taskbar at the end of a test.
+     */
+    @VisibleForTesting
+    public void unstashTaskbarIfStashed() {
+        mControllers.taskbarStashController.onLongPressToUnstashTaskbar();
+    }
+
     protected boolean isUserSetupComplete() {
         return mIsUserSetupComplete;
     }
@@ -808,6 +868,10 @@
         btv.post(() -> mControllers.taskbarPopupController.showForIcon(btv));
     }
 
+    public boolean isInApp() {
+        return mControllers.taskbarStashController.isInApp();
+    }
+
     protected void dumpLogs(String prefix, PrintWriter pw) {
         pw.println(prefix + "TaskbarActivityContext:");
 
@@ -822,6 +886,6 @@
         pw.println(String.format(
                 "%s\tmBindInProgress=%b", prefix, mBindingItems));
         mControllers.dumpLogs(prefix + "\t", pw);
-        mDeviceProfile.dump(prefix, pw);
+        mDeviceProfile.dump(this, prefix, pw);
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendController.java
index c5615c7..3cf9c99 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendController.java
@@ -71,8 +71,7 @@
     public void dumpLogs(String prefix, PrintWriter pw) {
         pw.println(prefix + "TaskbarAutohideSuspendController:");
 
-        pw.println(String.format(
-                "%s\tmAutohideSuspendFlags=%s", prefix, getStateString(mAutohideSuspendFlags)));
+        pw.println(prefix + "\tmAutohideSuspendFlags=" + getStateString(mAutohideSuspendFlags));
     }
 
     private static String getStateString(int flags) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
index 449e0a7..2b80b75 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -52,6 +52,8 @@
     public final TaskbarForceVisibleImmersiveController taskbarForceVisibleImmersiveController;
     public final TaskbarAllAppsController taskbarAllAppsController;
     public final TaskbarInsetsController taskbarInsetsController;
+    public final VoiceInteractionWindowController voiceInteractionWindowController;
+    public final TaskbarRecentAppsController taskbarRecentAppsController;
 
     @Nullable private LoggableTaskbarController[] mControllersToLog = null;
 
@@ -80,7 +82,9 @@
             TaskbarPopupController taskbarPopupController,
             TaskbarForceVisibleImmersiveController taskbarForceVisibleImmersiveController,
             TaskbarAllAppsController taskbarAllAppsController,
-            TaskbarInsetsController taskbarInsetsController) {
+            TaskbarInsetsController taskbarInsetsController,
+            VoiceInteractionWindowController voiceInteractionWindowController,
+            TaskbarRecentAppsController taskbarRecentAppsController) {
         this.taskbarActivityContext = taskbarActivityContext;
         this.taskbarDragController = taskbarDragController;
         this.navButtonController = navButtonController;
@@ -99,6 +103,8 @@
         this.taskbarForceVisibleImmersiveController = taskbarForceVisibleImmersiveController;
         this.taskbarAllAppsController = taskbarAllAppsController;
         this.taskbarInsetsController = taskbarInsetsController;
+        this.voiceInteractionWindowController = voiceInteractionWindowController;
+        this.taskbarRecentAppsController = taskbarRecentAppsController;
     }
 
     /**
@@ -126,13 +132,16 @@
         taskbarAllAppsController.init(this, sharedState.allAppsVisible);
         navButtonController.init(this);
         taskbarInsetsController.init(this);
+        voiceInteractionWindowController.init(this);
+        taskbarRecentAppsController.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 +181,8 @@
         taskbarAllAppsController.onDestroy();
         navButtonController.onDestroy();
         taskbarInsetsController.onDestroy();
+        voiceInteractionWindowController.onDestroy();
+        taskbarRecentAppsController.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..9a1e064 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;
@@ -31,6 +32,7 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.UserHandle;
+import android.util.Pair;
 import android.view.DragEvent;
 import android.view.MotionEvent;
 import android.view.SurfaceControl;
@@ -41,7 +43,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.logging.InstanceId;
-import com.android.internal.logging.InstanceIdSequence;
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.DragSource;
@@ -65,9 +66,10 @@
 import com.android.launcher3.shortcuts.DeepShortcutView;
 import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
 import com.android.launcher3.testing.TestLogging;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.quickstep.util.LogUtils;
 import com.android.systemui.shared.recents.model.Task;
 
 import java.io.PrintWriter;
@@ -358,11 +360,11 @@
         }
 
         if (clipDescription != null && intent != null) {
+            Pair<InstanceId, com.android.launcher3.logging.InstanceId> instanceIds =
+                    LogUtils.getShellShareableInstanceId();
             // Need to share the same InstanceId between launcher3 and WM Shell (internal).
-            InstanceId internalInstanceId = new InstanceIdSequence(
-                    com.android.launcher3.logging.InstanceId.INSTANCE_ID_MAX).newInstanceId();
-            com.android.launcher3.logging.InstanceId launcherInstanceId =
-                    new com.android.launcher3.logging.InstanceId(internalInstanceId.getId());
+            InstanceId internalInstanceId = instanceIds.first;
+            com.android.launcher3.logging.InstanceId launcherInstanceId = instanceIds.second;
 
             intent.putExtra(ClipDescription.EXTRA_LOGGING_INSTANCE_ID, internalInstanceId);
 
@@ -435,7 +437,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) {
@@ -558,13 +560,11 @@
     public void dumpLogs(String prefix, PrintWriter pw) {
         pw.println(prefix + "TaskbarDragController:");
 
-        pw.println(String.format("%s\tmDragIconSize=%dpx", prefix, mDragIconSize));
-        pw.println(String.format("%s\tmTempXY=%s", prefix, Arrays.toString(mTempXY)));
-        pw.println(String.format("%s\tmRegistrationX=%d", prefix, mRegistrationX));
-        pw.println(String.format("%s\tmRegistrationY=%d", prefix, mRegistrationY));
-        pw.println(String.format(
-                "%s\tmIsSystemDragInProgress=%b", prefix, mIsSystemDragInProgress));
-        pw.println(String.format(
-                "%s\tisInternalDragInProgess=%b", prefix, super.isDragging()));
+        pw.println(prefix + "\tmDragIconSize=" + mDragIconSize);
+        pw.println(prefix + "\tmTempXY=" + Arrays.toString(mTempXY));
+        pw.println(prefix + "\tmRegistrationX=" + mRegistrationX);
+        pw.println(prefix + "\tmRegistrationY=" + mRegistrationY);
+        pw.println(prefix + "\tmIsSystemDragInProgress=" + mIsSystemDragInProgress);
+        pw.println(prefix + "\tisInternalDragInProgess=" + super.isDragging());
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
index c1a6185..7e75779 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
@@ -24,17 +24,15 @@
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewTreeObserver;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.testing.TestLogging;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.views.BaseDragLayer;
-import com.android.systemui.shared.system.ViewTreeObserverWrapper;
-import com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo;
-import com.android.systemui.shared.system.ViewTreeObserverWrapper.OnComputeInsetsListener;
 
 /**
  * Top-level ViewGroup that hosts the TaskbarView as well as Views created by it such as Folder.
@@ -42,7 +40,8 @@
 public class TaskbarDragLayer extends BaseDragLayer<TaskbarActivityContext> {
 
     private final TaskbarBackgroundRenderer mBackgroundRenderer;
-    private final OnComputeInsetsListener mTaskbarInsetsComputer = this::onComputeTaskbarInsets;
+    private final ViewTreeObserver.OnComputeInternalInsetsListener mTaskbarInsetsComputer =
+            this::onComputeTaskbarInsets;
 
     // Initialized in init.
     private TaskbarDragLayerController.TaskbarDragLayerCallbacks mControllerCallbacks;
@@ -80,28 +79,33 @@
         mControllers = mControllerCallbacks.getTouchControllers();
     }
 
-    private void onComputeTaskbarInsets(InsetsInfo insetsInfo) {
+    private void onComputeTaskbarInsets(ViewTreeObserver.InternalInsetsInfo insetsInfo) {
         if (mControllerCallbacks != null) {
             mControllerCallbacks.updateInsetsTouchability(insetsInfo);
         }
     }
 
+    protected void onDestroy(boolean forceDestroy) {
+        if (forceDestroy) {
+            getViewTreeObserver().removeOnComputeInternalInsetsListener(mTaskbarInsetsComputer);
+        }
+    }
+
     protected void onDestroy() {
-        ViewTreeObserverWrapper.removeOnComputeInsetsListener(mTaskbarInsetsComputer);
+        onDestroy(!TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW);
     }
 
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        ViewTreeObserverWrapper.addOnComputeInsetsListener(getViewTreeObserver(),
-                mTaskbarInsetsComputer);
+        getViewTreeObserver().addOnComputeInternalInsetsListener(mTaskbarInsetsComputer);
     }
 
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
 
-        onDestroy();
+        onDestroy(true);
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
index 99c59a8..025fe7a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
@@ -17,11 +17,12 @@
 
 import android.content.res.Resources;
 import android.graphics.Rect;
+import android.view.ViewTreeObserver;
 
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.util.TouchController;
 import com.android.quickstep.AnimatedFloat;
-import com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo;
 
 import java.io.PrintWriter;
 
@@ -144,10 +145,9 @@
     public void dumpLogs(String prefix, PrintWriter pw) {
         pw.println(prefix + "TaskbarDragLayerController:");
 
-        pw.println(String.format("%s\tmBgOffset=%.2f", prefix, mBgOffset.value));
-        pw.println(String.format("%s\tmFolderMargin=%dpx", prefix, mFolderMargin));
-        pw.println(String.format(
-                "%s\tmLastSetBackgroundAlpha=%.2f", prefix, mLastSetBackgroundAlpha));
+        pw.println(prefix + "\tmBgOffset=" + mBgOffset.value);
+        pw.println(prefix + "\tmFolderMargin=" + mFolderMargin);
+        pw.println(prefix + "\tmLastSetBackgroundAlpha=" + mLastSetBackgroundAlpha);
     }
 
     /**
@@ -157,9 +157,9 @@
 
         /**
          * Called to update the touchable insets.
-         * @see InsetsInfo#setTouchableInsets(int)
+         * @see ViewTreeObserver.InternalInsetsInfo#setTouchableInsets(int)
          */
-        public void updateInsetsTouchability(InsetsInfo insetsInfo) {
+        public void updateInsetsTouchability(ViewTreeObserver.InternalInsetsInfo insetsInfo) {
             mControllers.taskbarInsetsController.updateInsetsTouchability(insetsInfo);
         }
 
@@ -174,7 +174,15 @@
          * Returns how tall the background should be drawn at the bottom of the screen.
          */
         public int getTaskbarBackgroundHeight() {
-            return mActivity.getDeviceProfile().taskbarSize;
+            DeviceProfile deviceProfile = mActivity.getDeviceProfile();
+            if (TaskbarManager.isPhoneMode(deviceProfile)) {
+                Resources resources = mActivity.getResources();
+                return mActivity.isThreeButtonNav() ?
+                        resources.getDimensionPixelSize(R.dimen.taskbar_size) :
+                        resources.getDimensionPixelSize(R.dimen.taskbar_stashed_size);
+            } else {
+                return deviceProfile.taskbarSize;
+            }
         }
 
         /**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduController.java
index e29b14b..32a3c10 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduController.java
@@ -191,12 +191,10 @@
     public void dumpLogs(String prefix, PrintWriter pw) {
         pw.println(prefix + "TaskbarEduController:");
 
-        pw.println(String.format("%s\tisShowingEdu=%b", prefix, mTaskbarEduView != null));
-        pw.println(String.format("%s\tmWaveAnimTranslationY=%.2f", prefix, mWaveAnimTranslationY));
-        pw.println(String.format(
-                "%s\tmWaveAnimTranslationYReturnOvershoot=%.2f",
-                prefix,
-                mWaveAnimTranslationYReturnOvershoot));
+        pw.println(prefix + "\tisShowingEdu=" + (mTaskbarEduView != null));
+        pw.println(prefix + "\tmWaveAnimTranslationY=" + mWaveAnimTranslationY);
+        pw.println(prefix + "\tmWaveAnimTranslationYReturnOvershoot="
+                + mWaveAnimTranslationYReturnOvershoot);
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarForceVisibleImmersiveController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarForceVisibleImmersiveController.java
index c99cebb..6c793a6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarForceVisibleImmersiveController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarForceVisibleImmersiveController.java
@@ -18,13 +18,17 @@
 
 import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS;
 import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
 
 import static com.android.launcher3.taskbar.NavbarButtonsViewController.ALPHA_INDEX_IMMERSIVE_MODE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IMMERSIVE_MODE;
 
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.view.MotionEvent;
+import android.view.View;
 
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.util.MultiValueAlpha;
@@ -52,6 +56,21 @@
             this::updateIconDimmingAlpha);
     private final Consumer<MultiValueAlpha> mImmersiveModeAlphaUpdater = alpha -> alpha.getProperty(
             ALPHA_INDEX_IMMERSIVE_MODE).setValue(mIconAlphaForDimming.value);
+    private final View.AccessibilityDelegate mKidsModeAccessibilityDelegate =
+            new View.AccessibilityDelegate() {
+                @Override
+                public boolean performAccessibilityAction(View host, int action, Bundle args) {
+                    if (action == ACTION_ACCESSIBILITY_FOCUS || action == ACTION_CLICK) {
+                        // Animate undimming of icons on an a11y event, followed by starting the
+                        // dimming animation (after its timeout has expired). Both can be called in
+                        // succession, as the playing of the two animations in a row is managed by
+                        // mHandler's message queue.
+                        startIconUndimming();
+                        startIconDimming();
+                    }
+                    return super.performAccessibilityAction(host, action, args);
+                }
+            };
 
     // Initialized in init.
     private TaskbarControllers mControllers;
@@ -77,12 +96,21 @@
             } else {
                 startIconUndimming();
             }
+            mControllers.navbarButtonsViewController.setHomeButtonAccessibilityDelegate(
+                    mKidsModeAccessibilityDelegate);
+            mControllers.navbarButtonsViewController.setBackButtonAccessibilityDelegate(
+                    mKidsModeAccessibilityDelegate);
+        } else {
+            mControllers.navbarButtonsViewController.setHomeButtonAccessibilityDelegate(null);
+            mControllers.navbarButtonsViewController.setBackButtonAccessibilityDelegate(null);
         }
     }
 
     /** Clean up animations. */
     public void onDestroy() {
         startIconUndimming();
+        mControllers.navbarButtonsViewController.setHomeButtonAccessibilityDelegate(null);
+        mControllers.navbarButtonsViewController.setBackButtonAccessibilityDelegate(null);
     }
 
     private void startIconUndimming() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
index 6a6a693..7b2b7ec 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
@@ -17,17 +17,23 @@
 
 import android.graphics.Insets
 import android.graphics.Region
+import android.view.InsetsFrameProvider
 import android.view.InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES
+import android.view.InsetsState
+import android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT
+import android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR
+import android.view.ViewTreeObserver
+import android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME
+import android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION
 import android.view.WindowManager
+import android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD
+import android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION
 import com.android.launcher3.AbstractFloatingView
 import com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_ALL_APPS
 import com.android.launcher3.DeviceProfile
 import com.android.launcher3.anim.AlphaUpdateListener
 import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
 import com.android.quickstep.KtR
-import com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo
-import com.android.systemui.shared.system.WindowManagerWrapper
-import com.android.systemui.shared.system.WindowManagerWrapper.*
 import java.io.PrintWriter
 
 /**
@@ -51,8 +57,7 @@
         this.controllers = controllers
         windowLayoutParams = context.windowLayoutParams
 
-        val wmWrapper: WindowManagerWrapper = getInstance()
-        wmWrapper.setProvidesInsetsTypes(
+        setProvidesInsetsTypes(
             windowLayoutParams,
             intArrayOf(
                 ITYPE_EXTRA_NAVIGATION_BAR,
@@ -61,9 +66,6 @@
             )
         )
 
-        windowLayoutParams.providedInternalInsets = arrayOfNulls<Insets>(ITYPE_SIZE)
-        windowLayoutParams.providedInternalImeInsets = arrayOfNulls<Insets>(ITYPE_SIZE)
-
         onTaskbarWindowHeightOrInsetsChanged()
 
         windowLayoutParams.insetsRoundedCornerFrame = true
@@ -75,39 +77,54 @@
     }
 
     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
+        val imeInsetsSize = Insets.of(0, 0, 0, taskbarHeightForIme)
+        // Use 0 insets for the VoiceInteractionWindow (assistant) when gesture nav is enabled.
+        val visInsetsSize = Insets.of(0, 0, 0, if (context.isGestureNav) 0 else tappableHeight)
+        val insetsSizeOverride = arrayOf(
+            InsetsFrameProvider.InsetsSizeOverride(
+                TYPE_INPUT_METHOD,
+                imeInsetsSize
+            ),
+            InsetsFrameProvider.InsetsSizeOverride(
+                TYPE_VOICE_INTERACTION,
+                visInsetsSize
+            )
+        )
+        for (provider in windowLayoutParams.providedInsets) {
+            provider.insetsSizeOverrides = insetsSizeOverride
+        }
     }
 
     /**
-     * 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.
+     * Sets {@param providesInsetsTypes} as the inset types provided by {@param params}.
+     * @param params The window layout params.
+     * @param providesInsetsTypes The inset types we would like this layout params to provide.
      */
-    private fun getReducingInsetsForTaskbarInsetsHeight(height: Int): Insets {
-        return Insets.of(0, windowLayoutParams.height - height, 0, 0)
+    fun setProvidesInsetsTypes(params: WindowManager.LayoutParams, providesInsetsTypes: IntArray) {
+        params.providedInsets = arrayOfNulls<InsetsFrameProvider>(providesInsetsTypes.size);
+        for (i in providesInsetsTypes.indices) {
+            params.providedInsets[i] = InsetsFrameProvider(providesInsetsTypes[i]);
+        }
     }
 
     /**
      * Called to update the touchable insets.
-     * @see InsetsInfo.setTouchableInsets
+     * @see InternalInsetsInfo.setTouchableInsets
      */
-    fun updateInsetsTouchability(insetsInfo: InsetsInfo) {
+    fun updateInsetsTouchability(insetsInfo: ViewTreeObserver.InternalInsetsInfo) {
         insetsInfo.touchableRegion.setEmpty()
         // Always have nav buttons be touchable
         controllers.navbarButtonsViewController.addVisibleButtonsRegion(
@@ -116,18 +133,18 @@
         var insetsIsTouchableRegion = true
         if (context.dragLayer.alpha < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD) {
             // Let touches pass through us.
-            insetsInfo.setTouchableInsets(InsetsInfo.TOUCHABLE_INSETS_REGION)
+            insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION)
         } else if (controllers.navbarButtonsViewController.isImeVisible) {
-            insetsInfo.setTouchableInsets(InsetsInfo.TOUCHABLE_INSETS_REGION)
+            insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION)
         } else if (!controllers.uiController.isTaskbarTouchable) {
             // Let touches pass through us.
-            insetsInfo.setTouchableInsets(InsetsInfo.TOUCHABLE_INSETS_REGION)
+            insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION)
         } else if (controllers.taskbarDragController.isSystemDragInProgress) {
             // Let touches pass through us.
-            insetsInfo.setTouchableInsets(InsetsInfo.TOUCHABLE_INSETS_REGION)
+            insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION)
         } else if (AbstractFloatingView.hasOpenView(context, TYPE_TASKBAR_ALL_APPS)) {
             // Let touches pass through us.
-            insetsInfo.setTouchableInsets(InsetsInfo.TOUCHABLE_INSETS_REGION)
+            insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION)
         } else if (controllers.taskbarViewController.areIconsVisible()
             || AbstractFloatingView.hasOpenView(context, AbstractFloatingView.TYPE_ALL)
             || context.isNavBarKidsModeActive
@@ -135,15 +152,15 @@
             // Taskbar has some touchable elements, take over the full taskbar area
             insetsInfo.setTouchableInsets(
                 if (context.isTaskbarWindowFullscreen) {
-                    InsetsInfo.TOUCHABLE_INSETS_FRAME
+                    TOUCHABLE_INSETS_FRAME
                 } else {
                     insetsInfo.touchableRegion.set(contentRegion)
-                    InsetsInfo.TOUCHABLE_INSETS_REGION
+                    TOUCHABLE_INSETS_REGION
                 }
             )
             insetsIsTouchableRegion = false
         } else {
-            insetsInfo.setTouchableInsets(InsetsInfo.TOUCHABLE_INSETS_REGION)
+            insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION)
         }
         context.excludeFromMagnificationRegion(insetsIsTouchableRegion)
     }
@@ -151,13 +168,18 @@
     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.print("$prefix\tprovidedInsets: (type=" + InsetsState.typeToString(provider.type)
+                    + " insetsSize=" + provider.insetsSize)
+            if (provider.insetsSizeOverrides != null) {
+                pw.print(" insetsSizeOverrides={")
+                for ((i, overrideSize) in provider.insetsSizeOverrides.withIndex()) {
+                    if (i > 0) pw.print(", ")
+                    pw.print(overrideSize)
+                }
+                pw.print("})")
+            }
+            pw.println()
+        }
     }
-}
\ No newline at end of file
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarKeyguardController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarKeyguardController.java
index 56648ea..0808fab 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarKeyguardController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarKeyguardController.java
@@ -111,11 +111,9 @@
     public void dumpLogs(String prefix, PrintWriter pw) {
         pw.println(prefix + "TaskbarKeyguardController:");
 
-        pw.println(String.format(
-                "%s\tmKeyguardSysuiFlags=%s",
-                prefix,
-                QuickStepContract.getSystemUiStateString(mKeyguardSysuiFlags)));
-        pw.println(String.format("%s\tmBouncerShowing=%b", prefix, mBouncerShowing));
-        pw.println(String.format("%s\tmIsScreenOff=%b", prefix, mIsScreenOff));
+        pw.println(prefix + "\tmKeyguardSysuiFlags=" + QuickStepContract.getSystemUiStateString(
+                mKeyguardSysuiFlags));
+        pw.println(prefix + "\tmBouncerShowing=" + mBouncerShowing);
+        pw.println(prefix + "\tmIsScreenOff=" + mIsScreenOff);
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index dc0ef27..58c689b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -19,20 +19,25 @@
 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;
 
 import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.QuickstepTransitionManager;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimatorListeners;
 import com.android.launcher3.statemanager.StateManager;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.quickstep.AnimatedFloat;
 import com.android.quickstep.RecentsAnimationCallbacks;
@@ -53,6 +58,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;
@@ -70,7 +78,7 @@
     private TaskbarControllers mControllers;
     private AnimatedFloat mTaskbarBackgroundAlpha;
     private MultiValueAlpha.AlphaProperty mIconAlphaForHome;
-    private BaseQuickstepLauncher mLauncher;
+    private QuickstepLauncher mLauncher;
 
     private Integer mPrevState;
     private int mState;
@@ -86,6 +94,19 @@
     // We skip any view synchronizations during init/destroy.
     private boolean mCanSyncViews;
 
+    private final Consumer<Float> mIconAlphaForHomeConsumer = alpha -> {
+        /*
+         * Hide Launcher Hotseat icons when Taskbar icons have opacity. Both icon sets
+         * should not be visible at the same time.
+         */
+        mLauncher.getHotseat().setIconsAlpha(alpha > 0 ? 0 : 1);
+        mLauncher.getHotseat().setQsbAlpha(
+                mLauncher.getDeviceProfile().isQsbInline && alpha > 0 ? 0 : 1);
+    };
+
+    private final DeviceProfile.OnDeviceProfileChangeListener mOnDeviceProfileChangeListener =
+            dp -> mIconAlphaForHomeConsumer.accept(mIconAlphaForHome.getValue());
+
     private final StateManager.StateListener<LauncherState> mStateListener =
             new StateManager.StateListener<LauncherState>() {
 
@@ -99,7 +120,11 @@
                     }
                     updateStateForFlag(FLAG_TRANSITION_STATE_RUNNING, true);
                     if (!mShouldDelayLauncherStateAnim) {
-                        applyState();
+                        if (toState == LauncherState.NORMAL) {
+                            applyState(QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION);
+                        } else {
+                            applyState();
+                        }
                     }
                 }
 
@@ -111,7 +136,7 @@
                 }
             };
 
-    public void init(TaskbarControllers controllers, BaseQuickstepLauncher launcher) {
+    public void init(TaskbarControllers controllers, QuickstepLauncher launcher) {
         mCanSyncViews = false;
 
         mControllers = controllers;
@@ -121,8 +146,7 @@
                 .getTaskbarBackgroundAlpha();
         MultiValueAlpha taskbarIconAlpha = mControllers.taskbarViewController.getTaskbarIconAlpha();
         mIconAlphaForHome = taskbarIconAlpha.getProperty(ALPHA_INDEX_HOME);
-        mIconAlphaForHome.setConsumer(
-                (Consumer<Float>) alpha -> mLauncher.getHotseat().setIconsAlpha(alpha > 0 ? 0 : 1));
+        mIconAlphaForHome.setConsumer(mIconAlphaForHomeConsumer);
 
         mIconAlignmentForResumedState.finishAnimation();
         onIconAlignmentRatioChangedForAppAndHomeTransition();
@@ -135,6 +159,7 @@
         applyState(0);
 
         mCanSyncViews = true;
+        mLauncher.addOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
     }
 
     public void onDestroy() {
@@ -149,6 +174,7 @@
         mLauncher.getStateManager().removeStateListener(mStateListener);
 
         mCanSyncViews = true;
+        mLauncher.removeOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
     }
 
     public Animator createAnimToLauncher(@NonNull LauncherState toState,
@@ -169,10 +195,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 +293,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 +334,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 +364,7 @@
                     .setDuration(duration));
         }
 
+        animatorSet.setInterpolator(EMPHASIZED);
         if (start) {
             animatorSet.start();
         }
@@ -344,11 +379,14 @@
     private void playStateTransitionAnim(AnimatorSet animatorSet, long duration,
             boolean committed) {
         boolean isInStashedState = mLauncherState.isTaskbarStashed(mLauncher);
-        float toAlignment = mLauncherState.isTaskbarAlignedWithHotseat(mLauncher) ? 1 : 0;
+        boolean willStashVisually =
+                isInStashedState && mControllers.taskbarStashController.supportsVisualStashing();
+        float toAlignment =
+                mLauncherState.isTaskbarAlignedWithHotseat(mLauncher) && !willStashVisually ? 1 : 0;
 
-        TaskbarStashController controller = mControllers.taskbarStashController;
-        controller.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE, isInStashedState);
-        Animator stashAnimator = controller.applyStateWithoutStart(duration);
+        TaskbarStashController stashController = mControllers.taskbarStashController;
+        stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE, isInStashedState);
+        Animator stashAnimator = stashController.applyStateWithoutStart(duration);
         if (stashAnimator != null) {
             stashAnimator.addListener(new AnimatorListenerAdapter() {
                 @Override
@@ -368,12 +406,22 @@
             });
             animatorSet.play(stashAnimator);
         }
-
-        // If we're already animating to the value, just leave it be instead of restarting it.
+        if (mIconAlignmentForLauncherState.value == toAlignment) {
+            // Already at expected value, but make sure we run the callback at the end.
+            animatorSet.addListener(AnimatorListeners.forEndCallback(
+                    this::onIconAlignmentRatioChangedForStateTransition));
+        }
         if (!mIconAlignmentForLauncherState.isAnimatingToValue(toAlignment)) {
+            // If we're already animating to the value, just leave it be instead of restarting it.
             mIconAlignmentForLauncherState.finishAnimation();
             animatorSet.play(mIconAlignmentForLauncherState.animateToValue(toAlignment)
                     .setDuration(duration));
+            if (DEBUG) {
+                Log.d(TAG, "mIconAlignmentForLauncherState - "
+                        + mIconAlignmentForLauncherState.value
+                        + " -> " + toAlignment + ": " + duration);
+            }
+            animatorSet.setInterpolator(EMPHASIZED);
         }
     }
 
@@ -396,17 +444,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 +464,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 +508,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/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 06262c0..bc69088 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -30,19 +30,22 @@
 import android.hardware.display.DisplayManager;
 import android.net.Uri;
 import android.os.Handler;
+import android.os.SystemProperties;
 import android.provider.Settings;
 import android.view.Display;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 
-import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.taskbar.unfold.NonDestroyableScopedUnfoldTransitionProgressProvider;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.NavigationMode;
 import com.android.launcher3.util.SettingsCache;
 import com.android.launcher3.util.SimpleBroadcastReceiver;
 import com.android.quickstep.RecentsActivity;
@@ -58,6 +61,9 @@
  */
 public class TaskbarManager {
 
+    public static final boolean FLAG_HIDE_NAVBAR_WINDOW =
+            SystemProperties.getBoolean("persist.wm.debug.hide_navbar_window", false);
+
     private static final Uri USER_SETUP_COMPLETE_URI = Settings.Secure.getUriFor(
             Settings.Secure.USER_SETUP_COMPLETE);
 
@@ -78,6 +84,7 @@
     // It's destruction/creation will be managed by the activity.
     private final ScopedUnfoldTransitionProgressProvider mUnfoldProgressProvider =
             new NonDestroyableScopedUnfoldTransitionProgressProvider();
+    private NavigationMode mNavMode;
 
     private TaskbarActivityContext mTaskbarActivityContext;
     private StatefulActivity mActivity;
@@ -128,9 +135,11 @@
                         | ActivityInfo.CONFIG_SCREEN_SIZE;
                 boolean requiresRecreate = (configDiff & configsRequiringRecreate) != 0;
                 if ((configDiff & ActivityInfo.CONFIG_SCREEN_SIZE) != 0
-                        && mTaskbarActivityContext != null && dp != null) {
+                        && mTaskbarActivityContext != null && dp != null
+                        && !isPhoneMode(dp)) {
                     // Additional check since this callback gets fired multiple times w/o
                     // screen size changing, or when simply rotating the device.
+                    // In the case of phone device rotation, we do want to call recreateTaskbar()
                     DeviceProfile oldDp = mTaskbarActivityContext.getDeviceProfile();
                     boolean isOrientationChange =
                             (configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0;
@@ -147,8 +156,8 @@
                 } else {
                     // Config change might be handled without re-creating the taskbar
                     if (mTaskbarActivityContext != null) {
-                        if (dp != null && dp.isTaskbarPresent) {
-                            mTaskbarActivityContext.updateDeviceProfile(dp);
+                        if (dp != null && isTaskbarPresent(dp)) {
+                            mTaskbarActivityContext.updateDeviceProfile(dp, mNavMode);
                         }
                         mTaskbarActivityContext.onConfigurationChanged(configDiff);
                     }
@@ -159,12 +168,15 @@
             @Override
             public void onLowMemory() { }
         };
-        mShutdownReceiver = new SimpleBroadcastReceiver(i -> destroyExistingTaskbar());
+        mShutdownReceiver = new SimpleBroadcastReceiver(i ->
+                destroyExistingTaskbar());
         mDispInfoChangeListener = (context, info, flags) -> {
             if ((flags & CHANGE_FLAGS) != 0) {
+                mNavMode = info.navigationMode;
                 recreateTaskbar();
             }
         };
+        mNavMode = mDisplayController.getInfo().navigationMode;
         mDisplayController.addChangeListener(mDispInfoChangeListener);
         SettingsCache.INSTANCE.get(mContext).register(USER_SETUP_COMPLETE_URI,
                 mUserSetupCompleteListener);
@@ -179,7 +191,9 @@
     private void destroyExistingTaskbar() {
         if (mTaskbarActivityContext != null) {
             mTaskbarActivityContext.onDestroy();
-            mTaskbarActivityContext = null;
+            if (!FLAG_HIDE_NAVBAR_WINDOW) {
+                mTaskbarActivityContext = null;
+            }
         }
     }
 
@@ -225,8 +239,8 @@
      */
     private UnfoldTransitionProgressProvider getUnfoldTransitionProgressProviderForActivity(
             StatefulActivity activity) {
-        if (activity instanceof BaseQuickstepLauncher) {
-            return ((BaseQuickstepLauncher) activity).getUnfoldTransitionProgressProvider();
+        if (activity instanceof QuickstepLauncher) {
+            return ((QuickstepLauncher) activity).getUnfoldTransitionProgressProvider();
         }
         return null;
     }
@@ -235,11 +249,11 @@
      * Creates a {@link TaskbarUIController} to use while the given StatefulActivity is active.
      */
     private TaskbarUIController createTaskbarUIControllerForActivity(StatefulActivity activity) {
-        if (activity instanceof BaseQuickstepLauncher) {
+        if (activity instanceof QuickstepLauncher) {
             if (mTaskbarActivityContext.getPackageManager().hasSystemFeature(FEATURE_PC)) {
-                return new DesktopTaskbarUIController((BaseQuickstepLauncher) activity);
+                return new DesktopTaskbarUIController((QuickstepLauncher) activity);
             }
-            return new LauncherTaskbarUIController((BaseQuickstepLauncher) activity);
+            return new LauncherTaskbarUIController((QuickstepLauncher) activity);
         }
         if (activity instanceof RecentsActivity) {
             return new FallbackTaskbarUIController((RecentsActivity) activity);
@@ -260,24 +274,33 @@
         }
     }
 
-    private void recreateTaskbar() {
+    /**
+     * This method is called multiple times (ex. initial init, then when user unlocks) in which case
+     * we fully want to destroy an existing taskbar and create a new one.
+     * In other case (folding/unfolding) we don't need to remove and add window.
+     */
+    @VisibleForTesting
+    public void recreateTaskbar() {
+        DeviceProfile dp = mUserUnlocked ?
+                LauncherAppState.getIDP(mContext).getDeviceProfile(mContext) : null;
+
         destroyExistingTaskbar();
 
-        DeviceProfile dp =
-                mUserUnlocked ? LauncherAppState.getIDP(mContext).getDeviceProfile(mContext) : null;
-
-        boolean isTaskBarEnabled = dp != null && dp.isTaskbarPresent;
-
+        boolean isTaskBarEnabled = dp != null && isTaskbarPresent(dp);
         if (!isTaskBarEnabled) {
             SystemUiProxy.INSTANCE.get(mContext)
                     .notifyTaskbarStatus(/* visible */ false, /* stashed */ false);
             return;
         }
 
-        mTaskbarActivityContext = new TaskbarActivityContext(mContext, dp, mNavButtonController,
-                mUnfoldProgressProvider);
-
+        if (mTaskbarActivityContext == null) {
+            mTaskbarActivityContext = new TaskbarActivityContext(mContext, dp, mNavButtonController,
+                    mUnfoldProgressProvider);
+        } else {
+            mTaskbarActivityContext.updateDeviceProfile(dp, mNavMode);
+        }
         mTaskbarActivityContext.init(mSharedState);
+
         if (mActivity != null) {
             mTaskbarActivityContext.setUIController(
                     createTaskbarUIControllerForActivity(mActivity));
@@ -301,6 +324,26 @@
         }
     }
 
+    /**
+     * @return {@code true} if provided device profile isn't a large screen profile
+     *                      and we are using a single window for taskbar and navbar.
+     */
+    public static boolean isPhoneMode(DeviceProfile deviceProfile) {
+        return TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW && deviceProfile.isPhone;
+    }
+
+    /**
+     * @return {@code true} if {@link #isPhoneMode(DeviceProfile)} is true and we're using
+     *                      3 button-nav
+     */
+    public static boolean isPhoneButtonNavMode(TaskbarActivityContext context) {
+        return isPhoneMode(context.getDeviceProfile()) && context.isThreeButtonNav();
+    }
+
+    private boolean isTaskbarPresent(DeviceProfile deviceProfile) {
+        return FLAG_HIDE_NAVBAR_WINDOW || deviceProfile.isTaskbarPresent;
+    }
+
     public void onRotationProposal(int rotation, boolean isValid) {
         if (mTaskbarActivityContext != null) {
             mTaskbarActivityContext.onRotationProposal(rotation, isValid);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
index 75881a3..5e670d294 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
@@ -29,6 +29,7 @@
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.LauncherBindableItemsContainer;
+import com.android.quickstep.RecentsModel;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -42,7 +43,7 @@
  * Launcher model Callbacks for rendering taskbar.
  */
 public class TaskbarModelCallbacks implements
-        BgDataModel.Callbacks, LauncherBindableItemsContainer {
+        BgDataModel.Callbacks, LauncherBindableItemsContainer, RecentsModel.RunningTasksListener {
 
     private final SparseArray<ItemInfo> mHotseatItems = new SparseArray<>();
     private List<ItemInfo> mPredictedItems = Collections.emptyList();
@@ -61,6 +62,16 @@
 
     public void init(TaskbarControllers controllers) {
         mControllers = controllers;
+        if (mControllers.taskbarRecentAppsController.isEnabled()) {
+            RecentsModel.INSTANCE.get(mContext).registerRunningTasksListener(this);
+        }
+    }
+
+    /**
+     * Unregisters listeners in this class.
+     */
+    public void unregisterListeners() {
+        RecentsModel.INSTANCE.get(mContext).unregisterRunningTasksListener();
     }
 
     @Override
@@ -185,6 +196,8 @@
                 isHotseatEmpty = false;
             }
         }
+        hotseatItemInfos = mControllers.taskbarRecentAppsController
+                .updateHotseatItemInfos(hotseatItemInfos);
         mContainer.updateHotseatItems(hotseatItemInfos);
 
         final boolean finalIsHotseatEmpty = isHotseatEmpty;
@@ -196,6 +209,21 @@
     }
 
     @Override
+    public void onRunningTasksChanged() {
+        updateRunningApps();
+    }
+
+    /** Called when there's a change in running apps to update the UI. */
+    public void commitRunningAppsToUI() {
+        commitItemsToUI();
+    }
+
+    /** Call TaskbarRecentAppsController to update running apps with mHotseatItems. */
+    public void updateRunningApps() {
+        mControllers.taskbarRecentAppsController.updateRunningApps(mHotseatItems);
+    }
+
+    @Override
     public void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMapCopy) {
         mControllers.taskbarPopupController.setDeepShortcutMap(deepShortcutMapCopy);
     }
@@ -203,6 +231,7 @@
     @Override
     public void bindAllApplications(AppInfo[] apps, int flags) {
         mControllers.taskbarAllAppsController.setApps(apps, flags);
+        mControllers.taskbarRecentAppsController.setApps(apps);
     }
 
     protected void dumpLogs(String prefix, PrintWriter pw) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
index 4ff0649..a395548 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
@@ -18,7 +18,6 @@
 
 import static com.android.internal.app.AssistUtils.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS;
 import static com.android.internal.app.AssistUtils.INVOCATION_TYPE_KEY;
-import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_A11Y_BUTTON_LONGPRESS;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_A11Y_BUTTON_TAP;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_BACK_BUTTON_LONGPRESS;
@@ -28,11 +27,14 @@
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_TAP;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_OVERVIEW_BUTTON_LONGPRESS;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_OVERVIEW_BUTTON_TAP;
+import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
 
 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;
@@ -41,7 +43,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.testing.TestLogging;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.quickstep.OverviewCommandHelper;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TaskUtils;
@@ -70,9 +72,8 @@
     public void dumpLogs(String prefix, PrintWriter pw) {
         pw.println(prefix + "TaskbarNavButtonController:");
 
-        pw.println(String.format(
-                "%s\tmLastScreenPinLongPress=%dms", prefix, mLastScreenPinLongPress));
-        pw.println(String.format("%s\tmScreenPinned=%b", prefix, mScreenPinned));
+        pw.println(prefix + "\tmLastScreenPinLongPress=" + mLastScreenPinLongPress);
+        pw.println(prefix + "\tmScreenPinned=" + mScreenPinned);
     }
 
     @Retention(RetentionPolicy.SOURCE)
@@ -113,7 +114,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 +147,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/TaskbarPopupController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
index 7b4501a..da6dab1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
@@ -15,14 +15,20 @@
  */
 package com.android.launcher3.taskbar;
 
+import static com.android.launcher3.util.SplitConfigurationOptions.getLogEventForPosition;
+
+import android.content.ClipDescription;
 import android.content.Intent;
 import android.content.pm.LauncherApps;
 import android.graphics.Point;
+import android.os.Bundle;
+import android.util.Pair;
 import android.view.MotionEvent;
 import android.view.View;
 
 import androidx.annotation.NonNull;
 
+import com.android.internal.logging.InstanceId;
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.LauncherSettings;
@@ -47,6 +53,7 @@
 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
 import com.android.launcher3.views.ActivityContext;
 import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.util.LogUtils;
 
 import java.io.PrintWriter;
 import java.util.HashMap;
@@ -263,8 +270,15 @@
 
         @Override
         public void onClick(View view) {
-            AbstractFloatingView.closeAllOpenViews(mTarget);
+            // Initiate splitscreen from the in-app Taskbar or Taskbar All Apps
+            Pair<InstanceId, com.android.launcher3.logging.InstanceId> instanceIds =
+                    LogUtils.getShellShareableInstanceId();
+            mTarget.getStatsLogManager().logger()
+                    .withItemInfo(mItemInfo)
+                    .withInstanceId(instanceIds.second)
+                    .log(getLogEventForPosition(mPosition.stagePosition));
 
+            AbstractFloatingView.closeAllOpenViews(mTarget);
             if (mItemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
                 WorkspaceItemInfo workspaceItemInfo = (WorkspaceItemInfo) mItemInfo;
                 SystemUiProxy.INSTANCE.get(mTarget).startShortcut(
@@ -272,7 +286,8 @@
                         workspaceItemInfo.getDeepShortcutId(),
                         mPosition.stagePosition,
                         null,
-                        workspaceItemInfo.user);
+                        workspaceItemInfo.user,
+                        instanceIds.first);
             } else {
                 SystemUiProxy.INSTANCE.get(mTarget).startIntent(
                         mTarget.getSystemService(LauncherApps.class).getMainActivityLaunchIntent(
@@ -281,7 +296,8 @@
                                 mItemInfo.user),
                         new Intent(),
                         mPosition.stagePosition,
-                        null);
+                        null,
+                        instanceIds.first);
             }
         }
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.java
new file mode 100644
index 0000000..8445cff
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import android.util.SparseArray;
+
+import androidx.annotation.CallSuper;
+
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.ItemInfo;
+
+/**
+ * Base class for providing recent apps functionality
+ */
+public class TaskbarRecentAppsController {
+
+    public static final TaskbarRecentAppsController DEFAULT = new TaskbarRecentAppsController();
+
+    // Initialized in init.
+    protected TaskbarControllers mControllers;
+
+    @CallSuper
+    protected void init(TaskbarControllers taskbarControllers) {
+        mControllers = taskbarControllers;
+    }
+
+    @CallSuper
+    protected void onDestroy() {
+        mControllers = null;
+    }
+
+    /** Stores the current {@link AppInfo} instances, no-op except in desktop environment. */
+    protected void setApps(AppInfo[] apps) { }
+
+    /**
+     * Indicates whether recent apps functionality is enabled, should return false except in
+     * desktop environment.
+     */
+    protected boolean isEnabled() {
+        return false;
+    }
+
+    /** Called to update hotseatItems, no-op except in desktop environment. */
+    protected ItemInfo[] updateHotseatItemInfos(ItemInfo[] hotseatItems) {
+        return hotseatItems;
+    }
+
+    /** Called to update the list of currently running apps, no-op except in desktop environment. */
+    protected void updateRunningApps(SparseArray<ItemInfo> hotseatItems) { }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java
index 58ace17..c3b0f57 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java
@@ -98,6 +98,6 @@
     public void dumpLogs(String prefix, PrintWriter pw) {
         pw.println(prefix + "TaskbarScrimViewController:");
 
-        pw.println(String.format("%s\tmScrimAlpha.value=%.2f", prefix, mScrimAlpha.value));
+        pw.println(prefix + "\tmScrimAlpha.value=" + mScrimAlpha.value);
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarShortcutMenuAccessibilityDelegate.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarShortcutMenuAccessibilityDelegate.java
index f131595..c10b57a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarShortcutMenuAccessibilityDelegate.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarShortcutMenuAccessibilityDelegate.java
@@ -17,24 +17,29 @@
 
 import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.DEEP_SHORTCUTS;
 import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.SHORTCUTS_AND_NOTIFICATIONS;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
+import static com.android.launcher3.util.SplitConfigurationOptions.getLogEventForPosition;
 
 import android.content.Intent;
 import android.content.pm.LauncherApps;
+import android.util.Pair;
 import android.view.KeyEvent;
 import android.view.View;
 
+import com.android.internal.logging.InstanceId;
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
 import com.android.launcher3.accessibility.BaseAccessibilityDelegate;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.notification.NotificationListener;
 import com.android.launcher3.util.ShortcutUtil;
 import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.util.LogUtils;
 
 import java.util.List;
 
@@ -49,10 +54,12 @@
     public static final int MOVE_TO_BOTTOM_OR_RIGHT = R.id.action_move_to_bottom_or_right;
 
     private final LauncherApps mLauncherApps;
+    private final StatsLogManager mStatsLogManager;
 
     public TaskbarShortcutMenuAccessibilityDelegate(TaskbarActivityContext context) {
         super(context);
         mLauncherApps = context.getSystemService(LauncherApps.class);
+        mStatsLogManager = context.getStatsLogManager();
 
         mActions.put(DEEP_SHORTCUTS, new LauncherAction(DEEP_SHORTCUTS,
                 R.string.action_deep_shortcut, KeyEvent.KEYCODE_S));
@@ -82,7 +89,14 @@
                 && (action == MOVE_TO_TOP_OR_LEFT || action == MOVE_TO_BOTTOM_OR_RIGHT)) {
             WorkspaceItemInfo info = (WorkspaceItemInfo) item;
             int side = action == MOVE_TO_TOP_OR_LEFT
-                    ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT;
+                    ? STAGE_POSITION_TOP_OR_LEFT : STAGE_POSITION_BOTTOM_OR_RIGHT;
+
+            Pair<InstanceId, com.android.launcher3.logging.InstanceId> instanceIds =
+                    LogUtils.getShellShareableInstanceId();
+            mStatsLogManager.logger()
+                    .withItemInfo(item)
+                    .withInstanceId(instanceIds.second)
+                    .log(getLogEventForPosition(side));
 
             if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
                 SystemUiProxy.INSTANCE.get(mContext).startShortcut(
@@ -90,14 +104,15 @@
                         info.getDeepShortcutId(),
                         side,
                         /* bundleOpts= */ null,
-                        info.user);
+                        info.user,
+                        instanceIds.first);
             } else {
                 SystemUiProxy.INSTANCE.get(mContext).startIntent(
                         mLauncherApps.getMainActivityLaunchIntent(
                                 item.getIntent().getComponent(),
                                 /* startActivityOptions= */null,
                                 item.user),
-                        new Intent(), side, null);
+                        new Intent(), side, null, instanceIds.first);
             }
             return true;
         } else if (action == DEEP_SHORTCUTS || action == SHORTCUTS_AND_NOTIFICATIONS) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index fc9f9d0..4b0adb1 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;
@@ -29,19 +30,23 @@
 import android.animation.AnimatorSet;
 import android.annotation.Nullable;
 import android.content.SharedPreferences;
+import android.content.res.Resources;
 import android.util.Log;
+import android.view.InsetsController;
+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.R;
 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.testing.shared.TestProtocol;
 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
 import com.android.quickstep.AnimatedFloat;
 import com.android.quickstep.SystemUiProxy;
-import com.android.systemui.shared.system.WindowManagerWrapper;
 
 import java.io.PrintWriter;
 import java.util.StringJoiner;
@@ -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
@@ -62,6 +69,7 @@
     public static final int FLAG_IN_STASHED_LAUNCHER_STATE = 1 << 6;
     public static final int FLAG_STASHED_IN_APP_ALL_APPS = 1 << 7; // All apps is visible.
     public static final int FLAG_IN_SETUP = 1 << 8; // In the Setup Wizard
+    public static final int FLAG_STASHED_SMALL_SCREEN = 1 << 9; // phone screen gesture nav, stashed
 
     // If any of these flags are enabled, isInApp should return true.
     private static final int FLAGS_IN_APP = FLAG_IN_APP | FLAG_IN_SETUP;
@@ -69,7 +77,8 @@
     // If we're in an app and any of these flags are enabled, taskbar should be stashed.
     private static final int FLAGS_STASHED_IN_APP = FLAG_STASHED_IN_APP_MANUAL
             | FLAG_STASHED_IN_APP_PINNED | FLAG_STASHED_IN_APP_EMPTY | FLAG_STASHED_IN_APP_SETUP
-            | FLAG_STASHED_IN_APP_IME | FLAG_STASHED_IN_APP_ALL_APPS;
+            | FLAG_STASHED_IN_APP_IME | FLAG_STASHED_IN_APP_ALL_APPS |
+            FLAG_STASHED_SMALL_SCREEN;
 
     private static final int FLAGS_STASHED_IN_APP_IGNORING_IME =
             FLAGS_STASHED_IN_APP & ~FLAG_STASHED_IN_APP_IME;
@@ -85,7 +94,7 @@
      * How long to stash/unstash when manually invoked via long press.
      */
     public static final long TASKBAR_STASH_DURATION =
-            WindowManagerWrapper.ANIMATION_DURATION_RESIZE;
+            InsetsController.ANIMATION_DURATION_RESIZE;
 
     /**
      * How long to stash/unstash when keyboard is appearing/disappearing.
@@ -149,8 +158,9 @@
     private @Nullable AnimatorSet mAnimator;
     private boolean mIsSystemGestureInProgress;
     private boolean mIsImeShowing;
+    private boolean mIsImeSwitcherShowing;
 
-    private boolean mEnableManualStashingForTests = false;
+    private boolean mEnableManualStashingDuringTests = false;
 
     // Evaluate whether the handle should be stashed
     private final StatePropertyHolder mStatePropertyHolder = new StatePropertyHolder(
@@ -158,15 +168,25 @@
                 boolean inApp = hasAnyFlag(flags, FLAGS_IN_APP);
                 boolean stashedInApp = hasAnyFlag(flags, FLAGS_STASHED_IN_APP);
                 boolean stashedLauncherState = hasAnyFlag(flags, FLAG_IN_STASHED_LAUNCHER_STATE);
-                return (inApp && stashedInApp) || (!inApp && stashedLauncherState);
+                boolean stashedForSmallScreen = hasAnyFlag(flags, FLAG_STASHED_SMALL_SCREEN);
+                return (inApp && stashedInApp) || (!inApp && stashedLauncherState)
+                        || stashedForSmallScreen;
             });
 
     public TaskbarStashController(TaskbarActivityContext activity) {
         mActivity = activity;
         mPrefs = Utilities.getPrefs(mActivity);
         mSystemUiProxy = SystemUiProxy.INSTANCE.get(activity);
-        mUnstashedHeight = mActivity.getDeviceProfile().taskbarSize;
-        mStashedHeight = mActivity.getDeviceProfile().stashedTaskbarSize;
+        if (isPhoneMode()) {
+            // DeviceProfile's taskbar vars aren't initialized w/ the flag off
+            Resources resources = mActivity.getResources();
+            mUnstashedHeight = resources.getDimensionPixelSize(R.dimen.taskbar_size);
+            mStashedHeight = resources.getDimensionPixelOffset(R.dimen.taskbar_stashed_size);
+        } else {
+            mUnstashedHeight = mActivity.getDeviceProfile().taskbarSize;
+            mStashedHeight = mActivity.getDeviceProfile().stashedTaskbarSize;
+        }
+
     }
 
     public void init(TaskbarControllers controllers, boolean setupUIVisible) {
@@ -188,13 +208,21 @@
                 StashedHandleViewController.ALPHA_INDEX_STASHED);
         mTaskbarStashedHandleHintScale = stashedHandleController.getStashedHandleHintScale();
 
-        boolean isManuallyStashedInApp = supportsManualStashing()
+        // We use supportsVisualStashing() here instead of supportsManualStashing() because we want
+        // it to work properly for tests that recreate taskbar. This check is here just to ensure
+        // that taskbar unstashes when going to 3 button mode (supportsVisualStashing() false).
+        boolean isManuallyStashedInApp = supportsVisualStashing()
                 && mPrefs.getBoolean(SHARED_PREFS_STASHED_KEY, DEFAULT_STASHED_PREF);
         boolean isInSetup = !mActivity.isUserSetupComplete() || setupUIVisible;
         updateStateForFlag(FLAG_STASHED_IN_APP_MANUAL, isManuallyStashedInApp);
         updateStateForFlag(FLAG_STASHED_IN_APP_SETUP, isInSetup);
         updateStateForFlag(FLAG_IN_SETUP, isInSetup);
-        applyState();
+        updateStateForFlag(FLAG_STASHED_SMALL_SCREEN, isPhoneMode()
+                && !mActivity.isThreeButtonNav());
+        // For now, assume we're in an app, since LauncherTaskbarUIController won't be able to tell
+        // us that we're paused until a bit later. This avoids flickering upon recreating taskbar.
+        updateStateForFlag(FLAG_IN_APP, true);
+        applyState(/* duration = */ 0);
 
         notifyStashChange(/* visible */ false, /* stashed */ isStashedInApp());
     }
@@ -204,7 +232,7 @@
      * state.
      */
     public boolean supportsVisualStashing() {
-        return mControllers.uiController.supportsVisualStashing();
+        return !mActivity.isThreeButtonNav() && mControllers.uiController.supportsVisualStashing();
     }
 
     /**
@@ -212,15 +240,15 @@
      */
     protected boolean supportsManualStashing() {
         return supportsVisualStashing()
-                && (!Utilities.IS_RUNNING_IN_TEST_HARNESS || mEnableManualStashingForTests);
+                && (!Utilities.IS_RUNNING_IN_TEST_HARNESS || mEnableManualStashingDuringTests);
     }
 
     /**
      * Enables support for manual stashing. This should only be used to add this functionality
      * to Launcher specific tests.
      */
-    public void enableManualStashingForTests(boolean enableManualStashing) {
-        mEnableManualStashingForTests = enableManualStashing;
+    public void enableManualStashingDuringTests(boolean enableManualStashing) {
+        mEnableManualStashingDuringTests = enableManualStashing;
     }
 
     /**
@@ -258,7 +286,14 @@
      * Returns whether the taskbar should be stashed in the current LauncherState.
      */
     public boolean isInStashedLauncherState() {
-        return hasAnyFlag(FLAG_IN_STASHED_LAUNCHER_STATE) && supportsVisualStashing();
+        return (hasAnyFlag(FLAG_IN_STASHED_LAUNCHER_STATE) && supportsVisualStashing());
+    }
+
+    /**
+     * @return {@code true} if we're not on a large screen AND using gesture nav
+     */
+    private boolean isPhoneMode() {
+        return TaskbarManager.isPhoneMode(mActivity.getDeviceProfile());
     }
 
     private boolean hasAnyFlag(int flagMask) {
@@ -287,6 +322,10 @@
      * @see WindowInsets.Type#systemBars()
      */
     public int getContentHeightToReportToApps() {
+        if (isPhoneMode() && !mActivity.isThreeButtonNav()) {
+            return getStashedHeight();
+        }
+
         if (supportsVisualStashing() && hasAnyFlag(FLAGS_REPORT_STASHED_INSETS_TO_APP)) {
             DeviceProfile dp = mActivity.getDeviceProfile();
             if (hasAnyFlag(FLAG_STASHED_IN_APP_SETUP) && dp.isTaskbarPresent && !dp.isLandscape) {
@@ -401,11 +440,19 @@
             mAnimator.cancel();
         }
         mAnimator = new AnimatorSet();
+        addJankMonitorListener(mAnimator, /* appearing= */ !mIsStashed);
+        final float stashTranslation = isPhoneMode() ? 0 :
+                (mUnstashedHeight - mStashedHeight) / 2f;
 
         if (!supportsVisualStashing()) {
             // Just hide/show the icons and background instead of stashing into a handle.
             mAnimator.play(mIconAlphaForStash.animateToValue(isStashed ? 0 : 1)
                     .setDuration(duration));
+            mAnimator.playTogether(mTaskbarBackgroundOffset.animateToValue(isStashed ? 1 : 0)
+                    .setDuration(duration));
+            mAnimator.playTogether(mIconTranslationYForStash.animateToValue(isStashed ?
+                            stashTranslation : 0)
+                    .setDuration(duration));
             mAnimator.play(mTaskbarImeBgAlpha.animateToValue(
                     hasAnyFlag(FLAG_STASHED_IN_APP_IME) ? 0 : 1).setDuration(duration));
             mAnimator.setStartDelay(startDelay);
@@ -429,7 +476,6 @@
         if (isStashed) {
             firstHalfDurationScale = 0.75f;
             secondHalfDurationScale = 0.5f;
-            final float stashTranslation = (mUnstashedHeight - mStashedHeight) / 2f;
 
             fullLengthAnimatorSet.play(mIconTranslationYForStash.animateToValue(stashTranslation));
             if (animateBg) {
@@ -441,7 +487,8 @@
 
             firstHalfAnimatorSet.playTogether(
                     mIconAlphaForStash.animateToValue(0),
-                    mIconScaleForStash.animateToValue(STASHED_TASKBAR_SCALE)
+                    mIconScaleForStash.animateToValue(isPhoneMode() ?
+                            0 : STASHED_TASKBAR_SCALE)
             );
             secondHalfAnimatorSet.playTogether(
                     mTaskbarStashedHandleAlpha.animateToValue(1)
@@ -496,6 +543,22 @@
         });
     }
 
+    private void addJankMonitorListener(AnimatorSet animator, boolean expanding) {
+        View v = mControllers.taskbarActivityContext.getDragLayer();
+        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 +634,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 +690,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 +700,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.
      *
@@ -690,15 +760,14 @@
     public void dumpLogs(String prefix, PrintWriter pw) {
         pw.println(prefix + "TaskbarStashController:");
 
-        pw.println(String.format("%s\tmStashedHeight=%dpx", prefix, mStashedHeight));
-        pw.println(String.format("%s\tmUnstashedHeight=%dpx", prefix, mUnstashedHeight));
-        pw.println(String.format("%s\tmIsStashed=%b", prefix, mIsStashed));
-        pw.println(String.format(
-                "%s\tappliedState=%s", prefix, getStateString(mStatePropertyHolder.mPrevFlags)));
-        pw.println(String.format("%s\tmState=%s", prefix, getStateString(mState)));
-        pw.println(String.format(
-                "%s\tmIsSystemGestureInProgress=%b", prefix, mIsSystemGestureInProgress));
-        pw.println(String.format("%s\tmIsImeShowing=%b", prefix, mIsImeShowing));
+        pw.println(prefix + "\tmStashedHeight=" + mStashedHeight);
+        pw.println(prefix + "\tmUnstashedHeight=" + mUnstashedHeight);
+        pw.println(prefix + "\tmIsStashed=" + mIsStashed);
+        pw.println(prefix + "\tappliedState=" + getStateString(mStatePropertyHolder.mPrevFlags));
+        pw.println(prefix + "\tmState=" + getStateString(mState));
+        pw.println(prefix + "\tmIsSystemGestureInProgress=" + mIsSystemGestureInProgress);
+        pw.println(prefix + "\tmIsImeShowing=" + mIsImeShowing);
+        pw.println(prefix + "\tmIsImeSwitcherShowing=" + mIsImeSwitcherShowing);
     }
 
     private static String getStateString(int flags) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index fcc34c6..114bfec 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -49,9 +49,13 @@
         return true;
     }
 
+    /**
+     * This should only be called by TaskbarStashController so that a TaskbarUIController can
+     * disable stashing. All other controllers should use
+     * {@link TaskbarStashController#supportsVisualStashing()} as the source of truth.
+     */
     public boolean supportsVisualStashing() {
-        if (mControllers == null) return false;
-        return !mControllers.taskbarActivityContext.isThreeButtonNav();
+        return true;
     }
 
     protected void onStashedInAppChanged() { }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 6f88d64..bb82d19 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -16,10 +16,14 @@
 package com.android.launcher3.taskbar;
 
 import android.content.Context;
+import android.content.Intent;
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Rect;
+import android.os.SystemProperties;
 import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
@@ -31,6 +35,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;
@@ -40,7 +45,6 @@
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.uioverrides.ApiWrapper;
 import com.android.launcher3.util.LauncherBindableItemsContainer;
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.views.AllAppsButton;
@@ -52,6 +56,7 @@
  * Hosts the Taskbar content such as Hotseat and Recent Apps. Drawn on top of other apps.
  */
 public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconParent, Insettable {
+    private static final String TAG = TaskbarView.class.getSimpleName();
 
     private static final float TASKBAR_BACKGROUND_LUMINANCE = 0.30f;
     public int mThemeIconsBackground;
@@ -78,6 +83,14 @@
     // Only non-null when device supports having an All Apps button.
     private @Nullable AllAppsButton mAllAppsButton;
 
+    private View mQsb;
+
+    // Only non-null when device supports having a floating task.
+    private @Nullable BubbleTextView mFloatingTaskButton;
+    private @Nullable Intent mFloatingTaskIntent;
+    private static final boolean FLOATING_TASKS_ENABLED =
+            SystemProperties.getBoolean("persist.wm.debug.floating_tasks", false);
+
     public TaskbarView(@NonNull Context context) {
         this(context, null);
     }
@@ -117,6 +130,22 @@
                     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);
+
+        if (FLOATING_TASKS_ENABLED) {
+            mFloatingTaskIntent = FloatingTaskIntentResolver.getIntent(context);
+            if (mFloatingTaskIntent != null) {
+                mFloatingTaskButton = new LaunchFloatingTaskButton(context);
+                mFloatingTaskButton.setLayoutParams(
+                        new ViewGroup.LayoutParams(mIconTouchSize, mIconTouchSize));
+                mFloatingTaskButton.setPadding(mItemPadding, mItemPadding, mItemPadding,
+                        mItemPadding);
+            } else {
+                Log.d(TAG, "Floating tasks is enabled but no intent was found!");
+            }
+        }
     }
 
     private int getColorWithGivenLuminance(int color, float luminance) {
@@ -144,6 +173,10 @@
         if (mAllAppsButton != null) {
             mAllAppsButton.setOnClickListener(mControllerCallbacks.getAllAppsButtonClickListener());
         }
+        if (mFloatingTaskButton != null) {
+            mFloatingTaskButton.setOnClickListener(
+                    mControllerCallbacks.getFloatingTaskButtonListener(mFloatingTaskIntent));
+        }
     }
 
     private void removeAndRecycle(View view) {
@@ -166,6 +199,11 @@
         if (mAllAppsButton != null) {
             removeView(mAllAppsButton);
         }
+        removeView(mQsb);
+
+        if (mFloatingTaskButton != null) {
+            removeView(mFloatingTaskButton);
+        }
 
         for (int i = 0; i < hotseatItemInfos.length; i++) {
             ItemInfo hotseatItemInfo = hotseatItemInfos[i];
@@ -242,6 +280,16 @@
             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);
+        }
+
+        if (mFloatingTaskButton != null) {
+            int index = Utilities.isRtl(getResources()) ? 0 : getChildCount();
+            addView(mFloatingTaskButton, index);
+        }
 
         mThemeIconsBackground = calculateThemeIconsBackground();
         setThemedIconsBackgroundColor(mThemeIconsBackground);
@@ -273,8 +321,13 @@
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         int count = getChildCount();
-        int spaceNeeded = count * (mItemMarginLeftRight * 2 + mIconTouchSize);
-        int navSpaceNeeded = ApiWrapper.getHotseatEndOffset(getContext());
+        int countExcludingQsb = count;
+        DeviceProfile deviceProfile = mActivityContext.getDeviceProfile();
+        if (deviceProfile.isQsbInline) {
+            countExcludingQsb--;
+        }
+        int spaceNeeded = countExcludingQsb * (mItemMarginLeftRight * 2 + mIconTouchSize);
+        int navSpaceNeeded = deviceProfile.hotseatBarEndOffset;
         boolean layoutRtl = isLayoutRtl();
         int iconEnd = right - (right - left - spaceNeeded) / 2;
         boolean needMoreSpaceForNav = layoutRtl ?
@@ -292,10 +345,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.hotseatQsbWidth;
+                } else {
+                    qsbEnd = iconEnd - mItemMarginLeftRight;
+                    qsbStart = qsbEnd - deviceProfile.hotseatQsbWidth;
+                }
+                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 +435,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..00d5083 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -16,12 +16,15 @@
 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;
+import static com.android.launcher3.taskbar.TaskbarManager.isPhoneMode;
 import static com.android.quickstep.AnimatedFloat.VALUE;
 
 import android.annotation.NonNull;
+import android.content.Intent;
 import android.graphics.Rect;
 import android.util.FloatProperty;
 import android.util.Log;
@@ -34,17 +37,22 @@
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.R;
 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;
 import com.android.quickstep.AnimatedFloat;
+import com.android.quickstep.SystemUiProxy;
 
 import java.io.PrintWriter;
 import java.util.function.Predicate;
@@ -63,7 +71,10 @@
     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;
+    public static final int ALPHA_INDEX_SMALL_SCREEN = 7;
+    private static final int NUM_ALPHA_CHANNELS = 8;
 
     private final TaskbarActivityContext mActivity;
     private final TaskbarView mTaskbarView;
@@ -102,7 +113,9 @@
     public void init(TaskbarControllers controllers) {
         mControllers = controllers;
         mTaskbarView.init(new TaskbarViewCallbacks());
-        mTaskbarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarSize;
+        mTaskbarView.getLayoutParams().height = isPhoneMode(mActivity.getDeviceProfile())
+                ? mActivity.getResources().getDimensionPixelSize(R.dimen.taskbar_size)
+                : mActivity.getDeviceProfile().taskbarSize;
         mThemeIconsColor = ThemedIconDrawable.getColors(mTaskbarView.getContext())[0];
 
         mTaskbarIconScaleForStash.updateValue(1f);
@@ -120,6 +133,7 @@
 
     public void onDestroy() {
         LauncherAppState.getInstance(mActivity).getModel().removeCallbacks(mModelCallbacks);
+        mModelCallbacks.unregisterListeners();
     }
 
     public boolean areIconsVisible() {
@@ -138,6 +152,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 +232,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 +247,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 +270,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 +284,45 @@
                 // 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.hotseatQsbWidth / 2f
+                        : hotseatPadding.left - borderSpacing - launcherDp.hotseatQsbWidth / 2f;
+                float childCenter = (child.getLeft() + child.getRight()) / 2f;
+                float halfQsbIconWidthDiff =
+                        (launcherDp.hotseatQsbWidth - 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.hotseatQsbWidth;
+                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;
@@ -326,9 +382,33 @@
     @Override
     public void dumpLogs(String prefix, PrintWriter pw) {
         pw.println(prefix + "TaskbarViewController:");
+
+        mTaskbarIconAlpha.dump(
+                prefix + "\t",
+                pw,
+                "mTaskbarIconAlpha",
+                "ALPHA_INDEX_HOME",
+                "ALPHA_INDEX_KEYGUARD",
+                "ALPHA_INDEX_STASH",
+                "ALPHA_INDEX_RECENTS_DISABLED",
+                "ALPHA_INDEX_NOTIFICATION_EXPANDED",
+                "ALPHA_INDEX_ASSISTANT_INVOKED",
+                "ALPHA_INDEX_IME_BUTTON_NAV",
+                "ALPHA_INDEX_SMALL_SCREEN");
+
         mModelCallbacks.dumpLogs(prefix + "\t", pw);
     }
 
+    /** Called when there's a change in running apps to update the UI. */
+    public void commitRunningAppsToUI() {
+        mModelCallbacks.commitRunningAppsToUI();
+    }
+
+    /** Call TaskbarModelCallbacks to update running apps. */
+    public void updateRunningApps() {
+        mModelCallbacks.updateRunningApps();
+    }
+
     /**
      * Callbacks for {@link TaskbarView} to interact with its controller.
      */
@@ -349,6 +429,13 @@
             };
         }
 
+        public View.OnClickListener getFloatingTaskButtonListener(@NonNull Intent intent) {
+            return v -> {
+                SystemUiProxy proxy = SystemUiProxy.INSTANCE.get(v.getContext());
+                proxy.showFloatingTask(intent);
+            };
+        }
+
         public View.OnLongClickListener getIconOnLongClickListener() {
             return mControllers.taskbarDragController::startDragOnLongClick;
         }
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..81acda3
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/VoiceInteractionWindowController.kt
@@ -0,0 +1,114 @@
+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)
+                    if (this@VoiceInteractionWindowController.context.isGestureNav
+                        && controllers.taskbarStashController.isInAppAndNotStashed) {
+                        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()
+        }
+
+        moveTaskbarBackgroundToAppropriateLayer()
+    }
+
+    /**
+     * Either:
+     * Hides the TaskbarDragLayer background and creates a new window to draw just that background.
+     * OR
+     * Removes the temporary window and show the TaskbarDragLayer background again.
+     */
+    private fun moveTaskbarBackgroundToAppropriateLayer() {
+        val taskbarBackgroundOverride = controllers.taskbarDragLayerController
+            .overrideBackgroundAlpha
+        val moveToLowerLayer = isVoiceInteractionWindowVisible
+        if (moveToLowerLayer) {
+            // 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/TaskbarAllAppsContext.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContext.java
index e2f7522..a1385c4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContext.java
@@ -17,15 +17,16 @@
 
 import static android.view.KeyEvent.ACTION_UP;
 import static android.view.KeyEvent.KEYCODE_BACK;
+import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
-import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_REGION;
-
 import android.content.Context;
 import android.graphics.Insets;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
 import android.view.WindowInsets;
 
 import com.android.launcher3.AbstractFloatingView;
@@ -43,13 +44,10 @@
 import com.android.launcher3.taskbar.TaskbarDragController;
 import com.android.launcher3.taskbar.TaskbarStashController;
 import com.android.launcher3.testing.TestLogging;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.util.OnboardingPrefs;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.views.BaseDragLayer;
-import com.android.systemui.shared.system.ViewTreeObserverWrapper;
-import com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo;
-import com.android.systemui.shared.system.ViewTreeObserverWrapper.OnComputeInsetsListener;
 
 /**
  * Window context for the taskbar all apps overlay.
@@ -163,7 +161,7 @@
 
     /** Root drag layer for this context. */
     private static class TaskbarAllAppsDragLayer extends
-            BaseDragLayer<TaskbarAllAppsContext> implements OnComputeInsetsListener {
+            BaseDragLayer<TaskbarAllAppsContext> implements OnComputeInternalInsetsListener {
 
         private TaskbarAllAppsDragLayer(Context context) {
             super(context, null, 1);
@@ -174,14 +172,13 @@
         @Override
         protected void onAttachedToWindow() {
             super.onAttachedToWindow();
-            ViewTreeObserverWrapper.addOnComputeInsetsListener(
-                    getViewTreeObserver(), this);
+            getViewTreeObserver().addOnComputeInternalInsetsListener(this);
         }
 
         @Override
         protected void onDetachedFromWindow() {
             super.onDetachedFromWindow();
-            ViewTreeObserverWrapper.removeOnComputeInsetsListener(this);
+            getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
         }
 
         @Override
@@ -207,7 +204,7 @@
         }
 
         @Override
-        public void onComputeInsets(InsetsInfo inoutInfo) {
+        public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
             if (mActivity.mDragController.isSystemDragInProgress()) {
                 inoutInfo.touchableRegion.setEmpty();
                 inoutInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
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/taskbar/allapps/TaskbarAllAppsSlideInView.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
index c4837a0..0e62da3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
@@ -16,8 +16,7 @@
 package com.android.launcher3.taskbar.allapps;
 
 import static com.android.launcher3.LauncherState.ALL_APPS;
-import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
-import static com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE;
+import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
 
 import android.animation.PropertyValuesHolder;
 import android.content.Context;
@@ -60,7 +59,7 @@
         if (animate) {
             mOpenCloseAnimator.setValues(
                     PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
-            mOpenCloseAnimator.setInterpolator(AGGRESSIVE_EASE);
+            mOpenCloseAnimator.setInterpolator(EMPHASIZED);
             mOpenCloseAnimator.setDuration(
                     ALL_APPS.getTransitionDuration(mActivityContext, true /* isToState */)).start();
         } else {
@@ -87,7 +86,7 @@
 
     @Override
     protected Interpolator getIdleInterpolator() {
-        return EMPHASIZED_ACCELERATE;
+        return EMPHASIZED;
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
index 2f8e4d9..f450496 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -17,14 +17,9 @@
 package com.android.launcher3.uioverrides;
 
 import android.app.Person;
-import android.content.Context;
 import android.content.pm.ShortcutInfo;
-import android.content.res.Resources;
 
-import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.DisplayController.NavigationMode;
 
 public class ApiWrapper {
 
@@ -34,24 +29,4 @@
         Person[] persons = si.getPersons();
         return persons == null ? Utilities.EMPTY_PERSON_ARRAY : persons;
     }
-
-    /**
-     * Returns the minimum space that should be left empty at the end of hotseat
-     */
-    public static int getHotseatEndOffset(Context context) {
-        if (DisplayController.getNavigationMode(context) == NavigationMode.THREE_BUTTONS) {
-            Resources res = context.getResources();
-            /*
-            * 3 nav buttons +
-            * Little space at the end for contextual buttons +
-            * Little space between icons and nav buttons
-            */
-            return 3 * res.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size)
-                    + res.getDimensionPixelSize(R.dimen.taskbar_contextual_button_margin)
-                    + res.getDimensionPixelSize(R.dimen.taskbar_hotseat_nav_spacing);
-        } else {
-            return 0;
-        }
-
-    }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index 84b3839..8fb7030 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -23,25 +23,32 @@
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SPLIT_SELECT_FLOATING_TASK_TRANSLATE_OFFSCREEN;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE;
 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.SKIP_OVERVIEW;
-import static com.android.launcher3.testing.TestProtocol.BAD_STATE;
+import static com.android.quickstep.views.FloatingTaskView.PRIMARY_TRANSLATE_OFFSCREEN;
 import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
 import static com.android.quickstep.views.RecentsView.RECENTS_GRID_PROGRESS;
 import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
 import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
+import static com.android.quickstep.views.RecentsView.TASK_THUMBNAIL_SPLASH_ALPHA;
 
+import android.graphics.Rect;
+import android.graphics.RectF;
 import android.util.FloatProperty;
-import android.util.Log;
+import android.view.animation.Interpolator;
 
 import androidx.annotation.NonNull;
 
-import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.statemanager.StateManager.StateHandler;
 import com.android.launcher3.states.StateAnimationConfig;
+import com.android.quickstep.views.FloatingTaskView;
 import com.android.quickstep.views.RecentsView;
 
 /**
@@ -53,9 +60,9 @@
 public abstract class BaseRecentsViewStateController<T extends RecentsView>
         implements StateHandler<LauncherState> {
     protected final T mRecentsView;
-    protected final BaseQuickstepLauncher mLauncher;
+    protected final QuickstepLauncher mLauncher;
 
-    public BaseRecentsViewStateController(@NonNull BaseQuickstepLauncher launcher) {
+    public BaseRecentsViewStateController(@NonNull QuickstepLauncher launcher) {
         mLauncher = launcher;
         mRecentsView = launcher.getOverviewPanel();
     }
@@ -67,24 +74,25 @@
         ADJACENT_PAGE_HORIZONTAL_OFFSET.set(mRecentsView, scaleAndOffset[1]);
         TASK_SECONDARY_TRANSLATION.set(mRecentsView, 0f);
 
-        float recentsAlpha = state.overviewUi ? 1f : 0;
-        Log.d(BAD_STATE, "BaseRecentsViewStateController setState state=" + state
-                + ", alpha=" + recentsAlpha);
-        getContentAlphaProperty().set(mRecentsView, recentsAlpha);
+        getContentAlphaProperty().set(mRecentsView, state.overviewUi ? 1f : 0);
         getTaskModalnessProperty().set(mRecentsView, state.getOverviewModalness());
         RECENTS_GRID_PROGRESS.set(mRecentsView,
                 state.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile()) ? 1f : 0f);
+        TASK_THUMBNAIL_SPLASH_ALPHA.set(mRecentsView, state.showTaskThumbnailSplash() ? 1f : 0f);
     }
 
     @Override
     public void setStateWithAnimation(LauncherState toState, StateAnimationConfig config,
             PendingAnimation builder) {
-        Log.d(BAD_STATE, "BaseRecentsViewStateController setStateWithAnimation state=" + toState
-                + ", config.skipOverview=" + config.hasAnimationFlag(SKIP_OVERVIEW));
         if (config.hasAnimationFlag(SKIP_OVERVIEW)) {
             return;
         }
         setStateWithAnimationInternal(toState, config, builder);
+        builder.addEndListener(success -> {
+            if (!success) {
+                mRecentsView.reset();
+            }
+        });
     }
 
     /**
@@ -104,19 +112,70 @@
         setter.setFloat(mRecentsView, TASK_SECONDARY_TRANSLATION, 0f,
                 config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, LINEAR));
 
-        float recentsAlpha = toState.overviewUi ? 1 : 0;
-        Log.d(BAD_STATE, "BaseRecentsViewStateController setStateWithAnimationInternal toState="
-                + toState + ", alpha=" + recentsAlpha);
-        setter.setFloat(mRecentsView, getContentAlphaProperty(), recentsAlpha,
+        if (mRecentsView.isSplitSelectionActive()) {
+            // TODO (b/238651489): Refactor state management to avoid need for double check
+            FloatingTaskView floatingTask = mRecentsView.getFirstFloatingTaskView();
+            if (floatingTask != null) {
+                // We are in split selection state currently, transitioning to another state
+                DragLayer dragLayer = mLauncher.getDragLayer();
+                RectF onScreenRectF = new RectF();
+                Utilities.getBoundsForViewInDragLayer(mLauncher.getDragLayer(), floatingTask,
+                        new Rect(0, 0, floatingTask.getWidth(), floatingTask.getHeight()),
+                        false, null, onScreenRectF);
+                // Get the part of the floatingTask that intersects with the DragLayer (i.e. the
+                // on-screen portion)
+                onScreenRectF.intersect(
+                        dragLayer.getLeft(),
+                        dragLayer.getTop(),
+                        dragLayer.getRight(),
+                        dragLayer.getBottom()
+                );
+
+                setter.setFloat(
+                        mRecentsView.getFirstFloatingTaskView(),
+                        PRIMARY_TRANSLATE_OFFSCREEN,
+                        mRecentsView.getPagedOrientationHandler()
+                                .getFloatingTaskOffscreenTranslationTarget(
+                                        floatingTask,
+                                        onScreenRectF,
+                                        floatingTask.getStagePosition(),
+                                        mLauncher.getDeviceProfile()
+                                ),
+                        config.getInterpolator(
+                                ANIM_OVERVIEW_SPLIT_SELECT_FLOATING_TASK_TRANSLATE_OFFSCREEN,
+                                LINEAR
+                        ));
+                setter.setViewAlpha(
+                        mRecentsView.getSplitInstructionsView(),
+                        0,
+                        config.getInterpolator(
+                                ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE,
+                                LINEAR
+                        )
+                );
+            }
+        }
+
+        setter.setFloat(mRecentsView, getContentAlphaProperty(), toState.overviewUi ? 1 : 0,
                 config.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
 
         setter.setFloat(
                 mRecentsView, getTaskModalnessProperty(),
                 toState.getOverviewModalness(),
                 config.getInterpolator(ANIM_OVERVIEW_MODAL, LINEAR));
+
+        LauncherState fromState = mLauncher.getStateManager().getState();
+        setter.setFloat(mRecentsView, TASK_THUMBNAIL_SPLASH_ALPHA,
+                toState.showTaskThumbnailSplash() ? 1f : 0f,
+                !toState.showTaskThumbnailSplash() && fromState == LauncherState.QUICK_SWITCH
+                        ? LINEAR : INSTANT);
+
         boolean showAsGrid = toState.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile());
+        Interpolator gridProgressInterpolator = showAsGrid
+                ? fromState == LauncherState.QUICK_SWITCH ? LINEAR : INSTANT
+                : FINAL_FRAME;
         setter.setFloat(mRecentsView, RECENTS_GRID_PROGRESS, showAsGrid ? 1f : 0f,
-                showAsGrid ? INSTANT : FINAL_FRAME);
+                gridProgressInterpolator);
     }
 
     abstract FloatProperty getTaskModalnessProperty();
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 4bb4343..192ac62 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -23,41 +23,83 @@
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
 import static com.android.launcher3.LauncherState.ALL_APPS;
 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_MODAL_TASK;
+import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
+import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_WIDGET_APP_START;
+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.config.FeatureFlags.ENABLE_WIDGET_PICKER_DEPTH;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
-import static com.android.launcher3.testing.TestProtocol.HINT_STATE_ORDINAL;
-import static com.android.launcher3.testing.TestProtocol.HINT_STATE_TWO_BUTTON_ORDINAL;
-import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
-import static com.android.launcher3.testing.TestProtocol.QUICK_SWITCH_STATE_ORDINAL;
+import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
+import static com.android.launcher3.popup.QuickstepSystemShortcut.getSplitSelectShortcutByPosition;
+import static com.android.launcher3.taskbar.LauncherTaskbarUIController.ALL_APPS_PAGE_PROGRESS_INDEX;
+import static com.android.launcher3.taskbar.LauncherTaskbarUIController.MINUS_ONE_PAGE_PROGRESS_INDEX;
+import static com.android.launcher3.taskbar.LauncherTaskbarUIController.WIDGETS_PAGE_PROGRESS_INDEX;
+import static com.android.launcher3.testing.shared.TestProtocol.HINT_STATE_ORDINAL;
+import static com.android.launcher3.testing.shared.TestProtocol.HINT_STATE_TWO_BUTTON_ORDINAL;
+import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_STATE_ORDINAL;
+import static com.android.launcher3.testing.shared.TestProtocol.QUICK_SWITCH_STATE_ORDINAL;
+import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
+import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
+import static com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.quickstep.util.BaseDepthController.WIDGET_DEPTH;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
 
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.content.Context;
 import android.content.Intent;
+import android.content.IntentSender;
 import android.content.SharedPreferences;
 import android.content.res.Configuration;
+import android.hardware.SensorManager;
+import android.hardware.devicestate.DeviceStateManager;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.IBinder;
+import android.os.SystemProperties;
+import android.view.Display;
 import android.view.HapticFeedbackConstants;
 import android.view.View;
+import android.window.SplashScreen;
 
-import com.android.launcher3.BaseQuickstepLauncher;
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.QuickstepAccessibilityDelegate;
+import com.android.launcher3.QuickstepTransitionManager;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.appprediction.PredictionRowView;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.hybridhotseat.HotseatPredictionController;
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.logging.StatsLogManager.StatsLogger;
 import com.android.launcher3.model.BgDataModel.FixedContainerItems;
+import com.android.launcher3.model.WellbeingModel;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.proxy.ProxyActivityStarter;
+import com.android.launcher3.proxy.StartActivityParams;
+import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
+import com.android.launcher3.statemanager.StateManager.StateHandler;
+import com.android.launcher3.taskbar.LauncherTaskbarUIController;
+import com.android.launcher3.taskbar.TaskbarManager;
 import com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory;
 import com.android.launcher3.uioverrides.touchcontrollers.NavBarToHomeTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.NoButtonNavbarToOverviewTouchController;
@@ -68,41 +110,98 @@
 import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.TwoButtonNavbarTouchController;
+import com.android.launcher3.util.ActivityOptionsWrapper;
 import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.DisplayController.NavigationMode;
+import com.android.launcher3.util.IntSet;
+import com.android.launcher3.util.NavigationMode;
+import com.android.launcher3.util.ObjectWrapper;
 import com.android.launcher3.util.PendingRequestArgs;
+import com.android.launcher3.util.PendingSplitSelectInfo;
+import com.android.launcher3.util.RunnableList;
+import com.android.launcher3.util.SafeCloseable;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
 import com.android.launcher3.util.TouchController;
-import com.android.launcher3.util.UiThreadHelper;
-import com.android.launcher3.util.UiThreadHelper.AsyncCommand;
 import com.android.launcher3.widget.LauncherAppWidgetHost;
+import com.android.quickstep.OverviewCommandHelper;
+import com.android.quickstep.RecentsModel;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TaskUtils;
+import com.android.quickstep.TouchInteractionService.TISBinder;
+import com.android.quickstep.util.LauncherUnfoldAnimationController;
+import com.android.quickstep.util.ProxyScreenStatusProvider;
 import com.android.quickstep.util.QuickstepOnboardingPrefs;
+import com.android.quickstep.util.RemoteAnimationProvider;
+import com.android.quickstep.util.RemoteFadeOutAnimationListener;
+import com.android.quickstep.util.SplitSelectStateController;
+import com.android.quickstep.util.TISBindHelper;
+import com.android.quickstep.util.ViewCapture;
+import com.android.quickstep.views.OverviewActionsView;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.unfold.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;
 import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 import java.util.function.Predicate;
 import java.util.stream.Stream;
 
-public class QuickstepLauncher extends BaseQuickstepLauncher {
+public class QuickstepLauncher extends Launcher {
+
+    public static final boolean ENABLE_PIP_KEEP_CLEAR_ALGORITHM =
+            SystemProperties.getBoolean("persist.wm.debug.enable_pip_keep_clear_algorithm", false);
 
     public static final boolean GO_LOW_RAM_RECENTS_ENABLED = false;
-    /**
-     * Reusable command for applying the shelf height on the background thread.
-     */
-    public static final AsyncCommand SET_SHELF_HEIGHT = (context, arg1, arg2) ->
-            SystemUiProxy.INSTANCE.get(context).setShelfHeight(arg1 != 0, arg2);
 
     private FixedContainerItems mAllAppsPredictions;
     private HotseatPredictionController mHotseatPredictionController;
+    private DepthController mDepthController;
+    private QuickstepTransitionManager mAppTransitionManager;
+    private OverviewActionsView mActionsView;
+    private TISBindHelper mTISBindHelper;
+    private @Nullable TaskbarManager mTaskbarManager;
+    private @Nullable OverviewCommandHelper mOverviewCommandHelper;
+    private @Nullable LauncherTaskbarUIController mTaskbarUIController;
+    // Will be updated when dragging from taskbar.
+    private @Nullable DragOptions mNextWorkspaceDragOptions = null;
+    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;
+
+    private SafeCloseable mViewCapture;
 
     @Override
     protected void setupViews() {
         super.setupViews();
+
+        mActionsView = findViewById(R.id.overview_actions_view);
+        RecentsView overviewPanel = (RecentsView) getOverviewPanel();
+        SplitSelectStateController controller =
+                new SplitSelectStateController(this, mHandler, getStateManager(),
+                        getDepthController(), getStatsLogManager());
+        overviewPanel.init(mActionsView, controller);
+        mActionsView.updateDimension(getDeviceProfile(), overviewPanel.getLastComputedTaskSize());
+        mActionsView.updateVerticalMargin(DisplayController.getNavigationMode(this));
+
+        mAppTransitionManager = new QuickstepTransitionManager(this);
+        mAppTransitionManager.registerRemoteAnimations();
+        mAppTransitionManager.registerRemoteTransitions();
+
+        mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
+        mDepthController = new DepthController(this);
         mHotseatPredictionController = new HotseatPredictionController(this);
     }
 
@@ -184,6 +283,16 @@
 
     @Override
     protected void onActivityFlagsChanged(int changeBits) {
+        if ((changeBits & ACTIVITY_STATE_STARTED) != 0) {
+            mDepthController.setActivityStarted(isStarted());
+        }
+
+        if ((changeBits & ACTIVITY_STATE_RESUMED) != 0) {
+            if (mTaskbarUIController != null) {
+                mTaskbarUIController.onLauncherResumedOrPaused(hasBeenResumed());
+            }
+        }
+
         super.onActivityFlagsChanged(changeBits);
         if ((changeBits & (ACTIVITY_STATE_DEFERRED_RESUMED | ACTIVITY_STATE_STARTED
                 | ACTIVITY_STATE_USER_ACTIVE | ACTIVITY_STATE_TRANSITION_ACTIVE)) != 0) {
@@ -204,8 +313,22 @@
 
     @Override
     public Stream<SystemShortcut.Factory> getSupportedShortcuts() {
+        Stream<SystemShortcut.Factory> base = Stream.of(WellbeingModel.SHORTCUT_FACTORY);
+        if (ENABLE_SPLIT_FROM_WORKSPACE.get() && mDeviceProfile.isTablet) {
+            RecentsView recentsView = getOverviewPanel();
+            // TODO: Pull it out of PagedOrentationHandler for split from workspace.
+            List<SplitPositionOption> positions =
+                    recentsView.getPagedOrientationHandler().getSplitPositionOptions(
+                            mDeviceProfile);
+            List<SystemShortcut.Factory<QuickstepLauncher>> splitShortcuts = new ArrayList<>();
+            for (SplitPositionOption position : positions) {
+                splitShortcuts.add(getSplitSelectShortcutByPosition(position));
+            }
+            base = Stream.concat(base, splitShortcuts.stream());
+        }
         return Stream.concat(
-                Stream.of(mHotseatPredictionController), super.getSupportedShortcuts());
+                Stream.of(mHotseatPredictionController),
+                Stream.concat(base, super.getSupportedShortcuts()));
     }
 
     /**
@@ -213,16 +336,17 @@
      */
     private void onStateOrResumeChanging(boolean inTransition) {
         LauncherState state = getStateManager().getState();
-        boolean started = ((getActivityFlags() & ACTIVITY_STATE_STARTED)) != 0;
-        if (started) {
-            DeviceProfile profile = getDeviceProfile();
-            boolean willUserBeActive =
-                    (getActivityFlags() & ACTIVITY_STATE_USER_WILL_BE_ACTIVE) != 0;
-            boolean visible = (state == NORMAL || state == OVERVIEW)
-                    && (willUserBeActive || isUserActive())
-                    && !profile.isVerticalBarLayout();
-            UiThreadHelper.runAsyncCommand(this, SET_SHELF_HEIGHT, visible ? 1 : 0,
-                    profile.hotseatBarSizePx);
+        if (!ENABLE_PIP_KEEP_CLEAR_ALGORITHM) {
+            boolean started = ((getActivityFlags() & ACTIVITY_STATE_STARTED)) != 0;
+            if (started) {
+                DeviceProfile profile = getDeviceProfile();
+                boolean willUserBeActive =
+                        (getActivityFlags() & ACTIVITY_STATE_USER_WILL_BE_ACTIVE) != 0;
+                boolean visible = (state == NORMAL || state == OVERVIEW)
+                        && (willUserBeActive || isUserActive())
+                        && !profile.isVerticalBarLayout();
+                SystemUiProxy.INSTANCE.get(this).setShelfHeight(visible, profile.hotseatBarSizePx);
+            }
         }
         if (state == NORMAL && !inTransition) {
             ((RecentsView) getOverviewPanel()).setSwipeDownShouldLaunchApp(false);
@@ -252,13 +376,29 @@
 
     @Override
     public void onDestroy() {
+        mAppTransitionManager.onActivityDestroyed();
+        if (mUnfoldTransitionProgressProvider != null) {
+            mUnfoldTransitionProgressProvider.destroy();
+        }
+
+        mTISBindHelper.onDestroy();
+        if (mTaskbarManager != null) {
+            mTaskbarManager.clearActivity(this);
+        }
+
+        if (mLauncherUnfoldAnimationController != null) {
+            mLauncherUnfoldAnimationController.onDestroy();
+        }
+
         super.onDestroy();
         mHotseatPredictionController.destroy();
+        mViewCapture.close();
     }
 
     @Override
     public void onStateSetEnd(LauncherState state) {
         super.onStateSetEnd(state);
+        handlePendingActivityRequest();
 
         switch (state.ordinal) {
             case HINT_STATE_ORDINAL: {
@@ -339,12 +479,459 @@
 
     protected LauncherAppWidgetHost createAppWidgetHost() {
         LauncherAppWidgetHost appWidgetHost = super.createAppWidgetHost();
-        if (ENABLE_QUICKSTEP_WIDGET_APP_START.get()) {
-            appWidgetHost.setInteractionHandler(new QuickstepInteractionHandler(this));
-        }
+        appWidgetHost.setInteractionHandler(new QuickstepInteractionHandler(this));
         return appWidgetHost;
     }
 
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (savedInstanceState != null) {
+            mPendingSplitSelectInfo = ObjectWrapper.unwrap(
+                    savedInstanceState.getIBinder(PENDING_SPLIT_SELECT_INFO));
+        }
+        addMultiWindowModeChangedListener(mDepthController);
+        initUnfoldTransitionProgressProvider();
+        mViewCapture = ViewCapture.INSTANCE.get(this).startCapture(getWindow());
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        if (mLauncherUnfoldAnimationController != null) {
+            mLauncherUnfoldAnimationController.onResume();
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        if (mLauncherUnfoldAnimationController != null) {
+            mLauncherUnfoldAnimationController.onPause();
+        }
+
+        super.onPause();
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+
+        if (mOverviewCommandHelper != null) {
+            mOverviewCommandHelper.clearPendingCommands();
+        }
+    }
+
+    public QuickstepTransitionManager getAppTransitionManager() {
+        return mAppTransitionManager;
+    }
+
+    @Override
+    public void onEnterAnimationComplete() {
+        super.onEnterAnimationComplete();
+        // After the transition to home, enable the high-res thumbnail loader if it wasn't enabled
+        // as a part of quickstep, so that high-res thumbnails can load the next time we enter
+        // overview
+        RecentsModel.INSTANCE.get(this).getThumbnailCache()
+                .getHighResLoadingState().setVisible(true);
+    }
+
+    @Override
+    protected void handleGestureContract(Intent intent) {
+        if (FeatureFlags.SEPARATE_RECENTS_ACTIVITY.get()) {
+            super.handleGestureContract(intent);
+        }
+    }
+
+    @Override
+    public void onTrimMemory(int level) {
+        super.onTrimMemory(level);
+        RecentsModel.INSTANCE.get(this).onTrimMemory(level);
+    }
+
+    @Override
+    public void onUiChangedWhileSleeping() {
+        // Remove the snapshot because the content view may have obvious changes.
+        UI_HELPER_EXECUTOR.execute(
+                () -> ActivityManagerWrapper.getInstance().invalidateHomeTaskSnapshot(this));
+    }
+
+    @Override
+    protected void onScreenOff() {
+        super.onScreenOff();
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            RecentsView recentsView = getOverviewPanel();
+            recentsView.finishRecentsAnimation(true /* toRecents */, null);
+        }
+    }
+
+    /**
+     * {@code LauncherOverlayCallbacks} scroll amount.
+     * Indicates transition progress to -1 screen.
+     * @param progress From 0 to 1.
+     */
+    @Override
+    public void onScrollChanged(float progress) {
+        super.onScrollChanged(progress);
+        onTaskbarInAppDisplayProgressUpdate(progress, MINUS_ONE_PAGE_PROGRESS_INDEX);
+    }
+
+    @Override
+    public void onAllAppsTransition(float progress) {
+        super.onAllAppsTransition(progress);
+        onTaskbarInAppDisplayProgressUpdate(progress, ALL_APPS_PAGE_PROGRESS_INDEX);
+    }
+
+    @Override
+    public void onWidgetsTransition(float progress) {
+        super.onWidgetsTransition(progress);
+        onTaskbarInAppDisplayProgressUpdate(progress, WIDGETS_PAGE_PROGRESS_INDEX);
+        // Change of wallpaper depth in widget picker is disabled for tests as it causes flakiness
+        // on very slow cuttlefish devices.
+        if (ENABLE_WIDGET_PICKER_DEPTH.get() && !Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+            WIDGET_DEPTH.set(getDepthController(),
+                    Utilities.mapToRange(progress, 0f, 1f, 0f, getDeviceProfile().bottomSheetDepth,
+                            EMPHASIZED));
+        }
+    }
+
+    private void onTaskbarInAppDisplayProgressUpdate(float progress, int flag) {
+        if (mTaskbarManager == null
+                || mTaskbarManager.getCurrentActivityContext() == null
+                || mTaskbarUIController == null) {
+            return;
+        }
+        mTaskbarUIController.onTaskbarInAppDisplayProgressUpdate(progress, flag);
+    }
+
+    @Override
+    public void startIntentSenderForResult(IntentSender intent, int requestCode,
+            Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options) {
+        if (requestCode != -1) {
+            mPendingActivityRequestCode = requestCode;
+            StartActivityParams params = new StartActivityParams(this, requestCode);
+            params.intentSender = intent;
+            params.fillInIntent = fillInIntent;
+            params.flagsMask = flagsMask;
+            params.flagsValues = flagsValues;
+            params.extraFlags = extraFlags;
+            params.options = options;
+            startActivity(ProxyActivityStarter.getLaunchIntent(this, params));
+        } else {
+            super.startIntentSenderForResult(intent, requestCode, fillInIntent, flagsMask,
+                    flagsValues, extraFlags, options);
+        }
+    }
+
+    @Override
+    public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
+        if (requestCode != -1) {
+            mPendingActivityRequestCode = requestCode;
+            StartActivityParams params = new StartActivityParams(this, requestCode);
+            params.intent = intent;
+            params.options = options;
+            startActivity(ProxyActivityStarter.getLaunchIntent(this, params));
+        } else {
+            super.startActivityForResult(intent, requestCode, options);
+        }
+    }
+
+    @Override
+    protected void onDeferredResumed() {
+        super.onDeferredResumed();
+        handlePendingActivityRequest();
+    }
+
+    private void handlePendingActivityRequest() {
+        if (mPendingActivityRequestCode != -1 && isInState(NORMAL)
+                && ((getActivityFlags() & ACTIVITY_STATE_DEFERRED_RESUMED) != 0)) {
+            // Remove any active ProxyActivityStarter task and send RESULT_CANCELED to Launcher.
+            onActivityResult(mPendingActivityRequestCode, RESULT_CANCELED, null);
+            // ProxyActivityStarter is started with clear task to reset the task after which it
+            // removes the task itself.
+            startActivity(ProxyActivityStarter.getLaunchIntent(this, null));
+        }
+    }
+
+    private void onTISConnected(TISBinder binder) {
+        mTaskbarManager = binder.getTaskbarManager();
+        mTaskbarManager.setActivity(this);
+        mOverviewCommandHelper = binder.getOverviewCommandHelper();
+    }
+
+    @Override
+    public void runOnBindToTouchInteractionService(Runnable r) {
+        mTISBindHelper.runOnBindToTouchInteractionService(r);
+    }
+
+    private void initUnfoldTransitionProgressProvider() {
+        final UnfoldTransitionConfig config = new ResourceUnfoldTransitionConfig();
+        if (config.isEnabled()) {
+            mUnfoldTransitionProgressProvider =
+                    UnfoldTransitionFactory.createUnfoldTransitionProgressProvider(
+                            /* context= */ this,
+                            config,
+                            ProxyScreenStatusProvider.INSTANCE,
+                            new DeviceStateManagerFoldProvider(
+                                    getSystemService(DeviceStateManager.class), /* context */this),
+                            new ActivityManagerActivityTypeProvider(
+                                    getSystemService(ActivityManager.class)),
+                            getSystemService(SensorManager.class),
+                            getMainThreadHandler(),
+                            getMainExecutor(),
+                            /* backgroundExecutor= */ THREAD_POOL_EXECUTOR,
+                            /* tracingTagPrefix= */ "launcher"
+                    );
+
+            mLauncherUnfoldAnimationController = new LauncherUnfoldAnimationController(
+                    this,
+                    getWindowManager(),
+                    mUnfoldTransitionProgressProvider
+            );
+        }
+    }
+
+    public void setTaskbarUIController(LauncherTaskbarUIController taskbarUIController) {
+        mTaskbarUIController = taskbarUIController;
+    }
+
+    public @Nullable LauncherTaskbarUIController getTaskbarUIController() {
+        return mTaskbarUIController;
+    }
+
+    public <T extends OverviewActionsView> T getActionsView() {
+        return (T) mActionsView;
+    }
+
+    @Override
+    protected void closeOpenViews(boolean animate) {
+        super.closeOpenViews(animate);
+        TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY);
+    }
+
+    @Override
+    protected void collectStateHandlers(List<StateHandler> out) {
+        super.collectStateHandlers(out);
+        out.add(getDepthController());
+        out.add(new RecentsViewStateController(this));
+    }
+
+    public DepthController getDepthController() {
+        return mDepthController;
+    }
+
+    @Nullable
+    public UnfoldTransitionProgressProvider getUnfoldTransitionProgressProvider() {
+        return mUnfoldTransitionProgressProvider;
+    }
+
+    @Override
+    public boolean supportsAdaptiveIconAnimation(View clickedView) {
+        return mAppTransitionManager.hasControlRemoteAppTransitionPermission();
+    }
+
+    @Override
+    public DragOptions getDefaultWorkspaceDragOptions() {
+        if (mNextWorkspaceDragOptions != null) {
+            DragOptions options = mNextWorkspaceDragOptions;
+            mNextWorkspaceDragOptions = null;
+            return options;
+        }
+        return super.getDefaultWorkspaceDragOptions();
+    }
+
+    public void setNextWorkspaceDragOptions(DragOptions dragOptions) {
+        mNextWorkspaceDragOptions = dragOptions;
+    }
+
+    @Override
+    public void useFadeOutAnimationForLauncherStart(CancellationSignal signal) {
+        QuickstepTransitionManager appTransitionManager = getAppTransitionManager();
+        appTransitionManager.setRemoteAnimationProvider(new RemoteAnimationProvider() {
+            @Override
+            public AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] appTargets,
+                    RemoteAnimationTargetCompat[] wallpaperTargets) {
+
+                // On the first call clear the reference.
+                signal.cancel();
+
+                ValueAnimator fadeAnimation = ValueAnimator.ofFloat(1, 0);
+                fadeAnimation.addUpdateListener(new RemoteFadeOutAnimationListener(appTargets,
+                        wallpaperTargets));
+                AnimatorSet anim = new AnimatorSet();
+                anim.play(fadeAnimation);
+                return anim;
+            }
+        }, signal);
+    }
+
+    @Override
+    public float[] getNormalOverviewScaleAndOffset() {
+        return DisplayController.getNavigationMode(this).hasGestures
+                ? new float[] {1, 1} : new float[] {1.1f, NO_OFFSET};
+    }
+
+    @Override
+    public void finishBindingItems(IntSet pagesBoundFirst) {
+        super.finishBindingItems(pagesBoundFirst);
+        // Instantiate and initialize WellbeingModel now that its loading won't interfere with
+        // populating workspace.
+        // TODO: Find a better place for this
+        WellbeingModel.INSTANCE.get(this);
+    }
+
+    @Override
+    public void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks) {
+        pendingTasks.add(() -> {
+            // This is added in pending task as we need to wait for views to be positioned
+            // correctly before registering them for the animation.
+            if (mLauncherUnfoldAnimationController != null) {
+                // This is needed in case items are rebound while the unfold animation is in
+                // progress.
+                mLauncherUnfoldAnimationController.updateRegisteredViewsIfNeeded();
+            }
+        });
+        super.onInitialBindComplete(boundPages, pendingTasks);
+    }
+
+    @Override
+    public ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) {
+        ActivityOptionsWrapper activityOptions =
+                mAppTransitionManager.hasControlRemoteAppTransitionPermission()
+                        ? mAppTransitionManager.getActivityLaunchOptions(v)
+                        : super.getActivityLaunchOptions(v, item);
+        if (mLastTouchUpTime > 0) {
+            activityOptions.options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_LAUNCHER,
+                    mLastTouchUpTime);
+        }
+        activityOptions.options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON);
+        activityOptions.options.setLaunchDisplayId(
+                (v != null && v.getDisplay() != null) ? v.getDisplay().getDisplayId()
+                        : Display.DEFAULT_DISPLAY);
+        addLaunchCookie(item, activityOptions.options);
+        return activityOptions;
+    }
+
+    /**
+     * Adds a new launch cookie for the activity launch if supported.
+     *
+     * @param info the item info for the launch
+     * @param opts the options to set the launchCookie on.
+     */
+    public void addLaunchCookie(ItemInfo info, ActivityOptions opts) {
+        IBinder launchCookie = getLaunchCookie(info);
+        if (launchCookie != null) {
+            opts.setLaunchCookie(launchCookie);
+        }
+    }
+
+    /**
+     * Return a new launch cookie for the activity launch if supported.
+     *
+     * @param info the item info for the launch
+     */
+    public IBinder getLaunchCookie(ItemInfo info) {
+        if (info == null) {
+            return null;
+        }
+        switch (info.container) {
+            case Favorites.CONTAINER_DESKTOP:
+            case Favorites.CONTAINER_HOTSEAT:
+                // Fall through and continue it's on the workspace (we don't support swiping back
+                // to other containers like all apps or the hotseat predictions (which can change)
+                break;
+            default:
+                if (info.container >= 0) {
+                    // Also allow swiping to folders
+                    break;
+                }
+                // Reset any existing launch cookies associated with the cookie
+                return ObjectWrapper.wrap(NO_MATCHING_ID);
+        }
+        switch (info.itemType) {
+            case Favorites.ITEM_TYPE_APPLICATION:
+            case Favorites.ITEM_TYPE_SHORTCUT:
+            case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
+            case Favorites.ITEM_TYPE_APPWIDGET:
+                // Fall through and continue if it's an app, shortcut, or widget
+                break;
+            default:
+                // Reset any existing launch cookies associated with the cookie
+                return ObjectWrapper.wrap(NO_MATCHING_ID);
+        }
+        return ObjectWrapper.wrap(new Integer(info.id));
+    }
+
+    public void setHintUserWillBeActive() {
+        addActivityFlags(ACTIVITY_STATE_USER_WILL_BE_ACTIVE);
+    }
+
+    @Override
+    public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) {
+        super.onDisplayInfoChanged(context, info, flags);
+        // When changing screens, force moving to rest state similar to StatefulActivity.onStop, as
+        // StatefulActivity isn't called consistently.
+        if ((flags & CHANGE_ACTIVE_SCREEN) != 0) {
+            getStateManager().moveToRestState();
+        }
+
+        if ((flags & CHANGE_NAVIGATION_MODE) != 0) {
+            getDragLayer().recreateControllers();
+            if (mActionsView != null) {
+                mActionsView.updateVerticalMargin(info.navigationMode);
+            }
+        }
+    }
+
+    @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()).getSplitSelectController();
+            // Launcher will restart in Overview and then transition to OverviewSplitSelect.
+            outState.putIBinder(PENDING_SPLIT_SELECT_INFO, ObjectWrapper.wrap(
+                    new PendingSplitSelectInfo(
+                            splitSelectStateController.getInitialTaskId(),
+                            splitSelectStateController.getActiveSplitStagePosition(),
+                            splitSelectStateController.getSplitEvent())
+            ));
+            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;
+    }
+
     private static final class LauncherTaskViewController extends
             TaskViewTouchController<Launcher> {
 
@@ -371,6 +958,9 @@
     @Override
     public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
         super.dump(prefix, fd, writer, args);
+        if (mDepthController != null) {
+            mDepthController.dump(prefix, writer);
+        }
         RecentsView recentsView = getOverviewPanel();
         writer.println("\nQuickstepLauncher:");
         writer.println(prefix + "\tmOrientationState: " + (recentsView == null ? "recentsNull" :
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 00a98c0..0723f8a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -27,15 +27,14 @@
 import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_SPLIT_TRANSLATION;
 import static com.android.quickstep.views.TaskView.FLAG_UPDATE_ALL;
 
+import android.animation.AnimatorSet;
 import android.annotation.TargetApi;
 import android.os.Build;
 import android.util.FloatProperty;
 import android.util.Pair;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 
-import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.anim.AnimatorListeners;
 import com.android.launcher3.anim.PendingAnimation;
@@ -55,7 +54,7 @@
 public final class RecentsViewStateController extends
         BaseRecentsViewStateController<LauncherRecentsView> {
 
-    public RecentsViewStateController(BaseQuickstepLauncher launcher) {
+    public RecentsViewStateController(QuickstepLauncher launcher) {
         super(launcher);
     }
 
@@ -72,7 +71,10 @@
         // DepthController to prevent optimizations which might occlude the layers behind
         mLauncher.getDepthController().setHasContentBehindLauncher(state.overviewUi);
 
-        handleSplitSelectionState(state, null);
+        PendingAnimation builder =
+                new PendingAnimation(state.getTransitionDuration(mLauncher, true));
+
+        handleSplitSelectionState(state, builder, /* animate */false);
     }
 
     @Override
@@ -93,7 +95,7 @@
         builder.addListener(AnimatorListeners.forSuccessCallback(() ->
                 mLauncher.getDepthController().setHasContentBehindLauncher(toState.overviewUi)));
 
-        handleSplitSelectionState(toState, builder);
+        handleSplitSelectionState(toState, builder, /* animate */true);
 
         setAlphas(builder, config, toState);
         builder.setFloat(mRecentsView, FULLSCREEN_PROGRESS,
@@ -106,8 +108,14 @@
      *                will add animations to builder.
      */
     private void handleSplitSelectionState(@NonNull LauncherState toState,
-            @Nullable PendingAnimation builder) {
-        boolean animate = builder != null;
+            @NonNull PendingAnimation builder, boolean animate) {
+        if (toState != OVERVIEW_SPLIT_SELECT) {
+            // Not going to split, nothing to do but ensure taskviews are at correct offset
+            mRecentsView.resetSplitPrimaryScrollOffset();
+            return;
+        }
+
+        // Create transition animations to split select
         PagedOrientationHandler orientationHandler =
                 ((RecentsView) mLauncher.getOverviewPanel()).getPagedOrientationHandler();
         Pair<FloatProperty, FloatProperty> taskViewsFloat =
@@ -115,25 +123,20 @@
                         TASK_PRIMARY_SPLIT_TRANSLATION, TASK_SECONDARY_SPLIT_TRANSLATION,
                         mLauncher.getDeviceProfile());
 
-        if (toState == OVERVIEW_SPLIT_SELECT) {
-            // Animation to "dismiss" selected taskView
-            PendingAnimation splitSelectInitAnimation = mRecentsView.createSplitSelectInitAnimation(
-                    toState.getTransitionDuration(mLauncher, true /* isToState */));
-            // Add properties to shift remaining taskViews to get out of placeholder view
-            splitSelectInitAnimation.setFloat(mRecentsView, taskViewsFloat.first,
-                    toState.getSplitSelectTranslation(mLauncher), LINEAR);
-            splitSelectInitAnimation.setFloat(mRecentsView, taskViewsFloat.second, 0, LINEAR);
+        mRecentsView.createSplitSelectInitAnimation(builder,
+                toState.getTransitionDuration(mLauncher, true /* isToState */));
+        // Add properties to shift remaining taskViews to get out of placeholder view
+        builder.setFloat(mRecentsView, taskViewsFloat.first,
+                toState.getSplitSelectTranslation(mLauncher), LINEAR);
+        builder.setFloat(mRecentsView, taskViewsFloat.second, 0, LINEAR);
 
-            if (!animate) {
-                splitSelectInitAnimation.buildAnim().start();
-            } else {
-                builder.add(splitSelectInitAnimation.buildAnim());
-            }
-
-            mRecentsView.applySplitPrimaryScrollOffset();
-        } else {
-            mRecentsView.resetSplitPrimaryScrollOffset();
+        if (!animate) {
+            AnimatorSet as = builder.buildAnim();
+            as.start();
+            as.end();
         }
+
+        mRecentsView.applySplitPrimaryScrollOffset();
     }
 
     private void setAlphas(PropertySetter propertySetter, StateAnimationConfig config,
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index a74774c..fd184c6 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -20,6 +20,7 @@
 
 import android.content.Context;
 
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.DeviceProfile.DeviceProfileListenable;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
@@ -31,8 +32,6 @@
  */
 public class AllAppsState extends LauncherState {
 
-    private static final float WORKSPACE_SCALE_FACTOR = 0.97f;
-
     private static final int STATE_FLAGS =
             FLAG_WORKSPACE_INACCESSIBLE | FLAG_CLOSE_POPUPS | FLAG_HOTSEAT_INACCESSIBLE;
 
@@ -43,9 +42,9 @@
     @Override
     public <DEVICE_PROFILE_CONTEXT extends Context & DeviceProfileListenable>
     int getTransitionDuration(DEVICE_PROFILE_CONTEXT context, boolean isToState) {
-        return !context.getDeviceProfile().isTablet && isToState
-                ? 600
-                : isToState ? 500 : 300;
+        return isToState
+                ? context.getDeviceProfile().allAppsOpenDuration
+                : context.getDeviceProfile().allAppsCloseDuration;
     }
 
     @Override
@@ -60,7 +59,8 @@
 
     @Override
     public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
-        return new ScaleAndTranslation(WORKSPACE_SCALE_FACTOR, NO_OFFSET, NO_OFFSET);
+        return new ScaleAndTranslation(launcher.getDeviceProfile().workspaceContentScale, NO_OFFSET,
+                NO_OFFSET);
     }
 
     @Override
@@ -71,17 +71,22 @@
             ScaleAndTranslation overviewScaleAndTranslation = LauncherState.OVERVIEW
                     .getWorkspaceScaleAndTranslation(launcher);
             return new ScaleAndTranslation(
-                    WORKSPACE_SCALE_FACTOR,
+                    launcher.getDeviceProfile().workspaceContentScale,
                     overviewScaleAndTranslation.translationX,
                     overviewScaleAndTranslation.translationY);
         }
     }
 
     @Override
-    protected float getDepthUnchecked(Context context) {
-        // The scrim fades in at approximately 50% of the swipe gesture.
-        // This means that the depth should be greater than 1, in order to fully zoom out.
-        return 2f;
+    protected <DEVICE_PROFILE_CONTEXT extends Context & DeviceProfile.DeviceProfileListenable>
+            float getDepthUnchecked(DEVICE_PROFILE_CONTEXT context) {
+        if (context.getDeviceProfile().isTablet) {
+            return context.getDeviceProfile().bottomSheetDepth;
+        } else {
+            // The scrim fades in at approximately 50% of the swipe gesture.
+            // This means that the depth should be greater than 1, in order to fully zoom out.
+            return 2f;
+        }
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index b733007..4150d40 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.uioverrides.states;
 
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
+import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
 
 import android.content.Context;
 import android.graphics.Color;
@@ -83,6 +84,11 @@
     }
 
     @Override
+    public boolean showTaskThumbnailSplash() {
+        return true;
+    }
+
+    @Override
     protected float getDepthUnchecked(Context context) {
         return 1;
     }
@@ -96,6 +102,12 @@
         return Color.TRANSPARENT;
     }
 
+    @Override
+    public boolean isTaskbarAlignedWithHotseat(Launcher launcher) {
+        if (ENABLE_SHELL_TRANSITIONS) return false;
+        return super.isTaskbarAlignedWithHotseat(launcher);
+    }
+
     public static float[] getOverviewScaleAndOffsetForBackgroundState(
             BaseDraggingActivity activity) {
         return new float[] {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 6427e09..6f07568 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -22,12 +22,10 @@
 import android.graphics.Rect;
 import android.os.SystemProperties;
 
-import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
-import com.android.launcher3.taskbar.LauncherTaskbarUIController;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.Themes;
 import com.android.quickstep.util.LayoutUtils;
@@ -39,6 +37,10 @@
  */
 public class OverviewState extends LauncherState {
 
+    private static final int OVERVIEW_SLIDE_IN_DURATION = 380;
+    private static final int OVERVIEW_POP_IN_DURATION = 250;
+    private static final int OVERVIEW_EXIT_DURATION = 250;
+
     protected static final Rect sTempRect = new Rect();
 
     private static final int STATE_FLAGS = FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED
@@ -59,8 +61,15 @@
 
     @Override
     public int getTransitionDuration(Context context, boolean isToState) {
-        // In gesture modes, overview comes in all the way from the side, so give it more time.
-        return DisplayController.getNavigationMode(context).hasGestures ? 380 : 250;
+        if (isToState) {
+            // In gesture modes, overview comes in all the way from the side, so give it more time.
+            return DisplayController.getNavigationMode(context).hasGestures
+                    ? OVERVIEW_SLIDE_IN_DURATION
+                    : OVERVIEW_POP_IN_DURATION;
+        } else {
+            // When exiting Overview, exit quickly.
+            return OVERVIEW_EXIT_DURATION;
+        }
     }
 
     @Override
@@ -95,13 +104,7 @@
 
     @Override
     public boolean isTaskbarStashed(Launcher launcher) {
-        if (launcher instanceof BaseQuickstepLauncher) {
-            LauncherTaskbarUIController uiController =
-                    ((BaseQuickstepLauncher) launcher).getTaskbarUIController();
-
-            return uiController != null && uiController.supportsVisualStashing();
-        }
-        return super.isTaskbarStashed(launcher);
+        return true;
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
index 2d7fe69..f75a404 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -22,16 +22,19 @@
 import static com.android.launcher3.LauncherState.HINT_STATE_TWO_BUTTON;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
 import static com.android.launcher3.WorkspaceStateTransitionAnimation.getWorkspaceSpringScaleAnimator;
 import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
-import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.launcher3.anim.Interpolators.EMPHASIZED_ACCELERATE;
+import static com.android.launcher3.anim.Interpolators.EMPHASIZED_DECELERATE;
 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.anim.Interpolators.OVERSHOOT_0_75;
 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
 import static com.android.launcher3.anim.Interpolators.clampToProgress;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
@@ -39,20 +42,15 @@
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SPLIT_SELECT_FLOATING_TASK_TRANSLATE_OFFSCREEN;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE;
 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,11 +58,12 @@
 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;
+import com.android.quickstep.util.SplitAnimationTimings;
 import com.android.quickstep.views.RecentsView;
 
 /**
@@ -94,9 +93,16 @@
     public void prepareForAtomicAnimation(LauncherState fromState, LauncherState toState,
             StateAnimationConfig config) {
         RecentsView overview = mActivity.getOverviewPanel();
-        if (toState == NORMAL && fromState == OVERVIEW) {
+        if ((fromState == OVERVIEW || fromState == OVERVIEW_SPLIT_SELECT) && toState == NORMAL) {
+            if (fromState == OVERVIEW_SPLIT_SELECT) {
+                config.setInterpolator(ANIM_OVERVIEW_SPLIT_SELECT_FLOATING_TASK_TRANSLATE_OFFSCREEN,
+                        clampToProgress(EMPHASIZED_ACCELERATE, 0, 0.4f));
+                config.setInterpolator(ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE,
+                        clampToProgress(LINEAR, 0, 0.33f));
+            }
+
             config.setInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, clampToProgress(LINEAR, 0, 0.25f));
-            config.setInterpolator(ANIM_SCRIM_FADE, LINEAR);
+            config.setInterpolator(ANIM_SCRIM_FADE, clampToProgress(LINEAR, 0.33f, 1));
             config.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL);
             config.setInterpolator(ANIM_WORKSPACE_FADE, ACCEL);
 
@@ -105,8 +111,7 @@
                 // Overview is going offscreen, so keep it at its current scale and opacity.
                 config.setInterpolator(ANIM_OVERVIEW_SCALE, FINAL_FRAME);
                 config.setInterpolator(ANIM_OVERVIEW_FADE, FINAL_FRAME);
-                config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X,
-                        clampToProgress(FAST_OUT_SLOW_IN, 0, 0.75f));
+                config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, EMPHASIZED_DECELERATE);
                 config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, FINAL_FRAME);
             } else {
                 config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL_DEACCEL);
@@ -182,23 +187,21 @@
             }
             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);
+        } else if (fromState == OVERVIEW && toState == OVERVIEW_SPLIT_SELECT) {
+            SplitAnimationTimings timings = SplitAnimationTimings.OVERVIEW_TO_SPLIT;
+            config.setInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, clampToProgress(LINEAR,
+                    timings.getActionsFadeStartOffset(),
+                    timings.getActionsFadeEndOffset()));
+        } else if (fromState == NORMAL && toState == OVERVIEW_SPLIT_SELECT) {
+            SplitAnimationTimings timings = SplitAnimationTimings.NORMAL_TO_SPLIT;
+            config.setInterpolator(ANIM_SCRIM_FADE, clampToProgress(LINEAR,
+                    timings.getScrimFadeInStartOffset(),
+                    timings.getScrimFadeInEndOffset()));
+            config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_0_75);
+            config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, OVERSHOOT_0_75);
         }
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
index e79d56b..430053d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
@@ -16,7 +16,10 @@
 
 package com.android.launcher3.uioverrides.states;
 
+import android.content.Context;
+
 import com.android.launcher3.Launcher;
+import com.android.quickstep.util.SplitAnimationTimings;
 import com.android.quickstep.views.RecentsView;
 
 /**
@@ -38,4 +41,11 @@
         RecentsView recentsView = launcher.getOverviewPanel();
         return recentsView.getSplitSelectTranslation();
     }
+
+    @Override
+    public int getTransitionDuration(Context context, boolean isToState) {
+        return isToState
+                ? SplitAnimationTimings.ENTER_DURATION
+                : SplitAnimationTimings.ABORT_DURATION;
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index 34a6821..30bb892 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -25,7 +25,6 @@
 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PULL_BACK_TRANSLATION;
 import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_EDU;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
@@ -106,7 +105,7 @@
         if (mStartState.overviewUi || mStartState == ALL_APPS) {
             return true;
         }
-        int typeToClose = ENABLE_ALL_APPS_EDU.get() ? TYPE_ALL & ~TYPE_ALL_APPS_EDU : TYPE_ALL;
+        int typeToClose = TYPE_ALL & ~TYPE_ALL_APPS_EDU;
         if (AbstractFloatingView.getTopOpenViewWithType(mLauncher, typeToClose) != null) {
             return true;
         }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index 8faabc9..918b3c1 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -34,13 +34,13 @@
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
 
-import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.taskbar.LauncherTaskbarUIController;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.util.MotionPauseDetector;
@@ -114,7 +114,7 @@
     public void onDragStart(boolean start, float startDisplacement) {
         if (mLauncher.isInState(ALL_APPS)) {
             LauncherTaskbarUIController controller =
-                    ((BaseQuickstepLauncher) mLauncher).getTaskbarUIController();
+                    ((QuickstepLauncher) mLauncher).getTaskbarUIController();
             if (controller != null) {
                 controller.setShouldDelayLauncherStateAnim(true);
             }
@@ -151,7 +151,7 @@
     @Override
     public void onDragEnd(float velocity) {
         LauncherTaskbarUIController controller =
-                ((BaseQuickstepLauncher) mLauncher).getTaskbarUIController();
+                ((QuickstepLauncher) mLauncher).getTaskbarUIController();
         if (controller != null) {
             controller.setShouldDelayLauncherStateAnim(false);
         }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 53dc9dd..922679b 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -39,7 +39,6 @@
 import static com.android.launcher3.states.StateAnimationConfig.SKIP_ALL_ANIMATIONS;
 import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
 import static com.android.launcher3.states.StateAnimationConfig.SKIP_SCRIM;
-import static com.android.launcher3.testing.TestProtocol.BAD_STATE;
 import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_RIGHT;
 import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_UP;
 import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
@@ -49,6 +48,7 @@
 import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
 import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
 import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
+import static com.android.quickstep.views.RecentsView.TASK_THUMBNAIL_SPLASH_ALPHA;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 
 import android.animation.Animator;
@@ -56,11 +56,9 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.graphics.PointF;
-import android.util.Log;
 import android.view.MotionEvent;
 import android.view.animation.Interpolator;
 
-import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
@@ -69,6 +67,7 @@
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.touch.BaseSwipeDetector;
 import com.android.launcher3.touch.BothAxesSwipeDetector;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.util.TouchController;
 import com.android.quickstep.AnimatedFloat;
 import com.android.quickstep.SystemUiProxy;
@@ -93,7 +92,7 @@
     private static final Interpolator SCALE_DOWN_INTERPOLATOR = LINEAR;
     private static final long ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW = 300;
 
-    private final BaseQuickstepLauncher mLauncher;
+    private final QuickstepLauncher mLauncher;
     private final BothAxesSwipeDetector mSwipeDetector;
     private final float mXRange;
     private final float mYRange;
@@ -115,7 +114,7 @@
     private AnimatorPlaybackController mXOverviewAnim;
     private AnimatedFloat mYOverviewAnim;
 
-    public NoButtonQuickSwitchTouchController(BaseQuickstepLauncher launcher) {
+    public NoButtonQuickSwitchTouchController(QuickstepLauncher launcher) {
         mLauncher = launcher;
         mSwipeDetector = new BothAxesSwipeDetector(mLauncher, this);
         mRecentsView = mLauncher.getOverviewPanel();
@@ -227,11 +226,12 @@
         // Set RecentView's initial properties.
         RECENTS_SCALE_PROPERTY.set(mRecentsView, fromState.getOverviewScaleAndOffset(mLauncher)[0]);
         ADJACENT_PAGE_HORIZONTAL_OFFSET.set(mRecentsView, 1f);
-        Log.d(BAD_STATE, "NBQSTC setupOverviewAnimators setContentAlpha=1");
+        TASK_THUMBNAIL_SPLASH_ALPHA.set(mRecentsView, fromState.showTaskThumbnailSplash() ? 1f : 0);
         mRecentsView.setContentAlpha(1);
         mRecentsView.setFullscreenProgress(fromState.getOverviewFullscreenProgress());
         mLauncher.getActionsView().getVisibilityAlpha().setValue(
                 (fromState.getVisibleElements(mLauncher) & OVERVIEW_ACTIONS) != 0 ? 1f : 0f);
+        mRecentsView.setTaskIconScaledDown(true);
 
         float[] scaleAndOffset = toState.getOverviewScaleAndOffset(mLauncher);
         // As we drag right, animate the following properties:
@@ -246,24 +246,6 @@
                 QUICK_SWITCH.getWorkspaceScrimColor(mLauncher), LINEAR);
         if (mRecentsView.getTaskViewCount() == 0) {
             xAnim.addFloat(mRecentsView, CONTENT_ALPHA, 0f, 1f, LINEAR);
-            Log.d(BAD_STATE, "NBQSTC setupOverviewAnimators from: 0 to: 1");
-            xAnim.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationStart(Animator animation) {
-                    Log.d(BAD_STATE, "NBQSTC setupOverviewAnimators onStart");
-                }
-
-                @Override
-                public void onAnimationCancel(Animator animation) {
-                    float alpha = mRecentsView == null ? -1 : CONTENT_ALPHA.get(mRecentsView);
-                    Log.d(BAD_STATE, "NBQSTC setupOverviewAnimators onCancel, alpha=" + alpha);
-                }
-
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    Log.d(BAD_STATE, "NBQSTC setupOverviewAnimators onEnd");
-                }
-            });
         }
         mXOverviewAnim = xAnim.createPlaybackController();
         mXOverviewAnim.dispatchOnStart();
@@ -321,6 +303,7 @@
         boolean verticalFling = mSwipeDetector.isFling(velocity.y);
         boolean noFling = !horizontalFling && !verticalFling;
         if (mMotionPauseDetector.isPaused() && noFling) {
+            // Going to Overview.
             cancelAnimations();
 
             StateAnimationConfig config = new StateAnimationConfig();
@@ -331,6 +314,8 @@
                 @Override
                 public void onAnimationEnd(Animator animation) {
                     onAnimationToStateCompleted(OVERVIEW);
+                    // Animate the icon after onAnimationToStateCompleted() so it doesn't clobber.
+                    mRecentsView.animateUpTaskIconScale();
                 }
             });
             overviewAnim.start();
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/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
index d1b0a9c..56ac4c5 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
@@ -29,7 +29,6 @@
 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_TRANSLATE;
-import static com.android.launcher3.testing.TestProtocol.BAD_STATE;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK;
 import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
 import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
@@ -37,7 +36,6 @@
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 
-import android.util.Log;
 import android.view.MotionEvent;
 
 import com.android.launcher3.Launcher;
@@ -47,7 +45,7 @@
 import com.android.launcher3.touch.AbstractStateChangeTouchController;
 import com.android.launcher3.touch.SingleAxisSwipeDetector;
 import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.DisplayController.NavigationMode;
+import com.android.launcher3.util.NavigationMode;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TaskUtils;
 import com.android.quickstep.views.RecentsView;
@@ -114,7 +112,6 @@
         RECENTS_SCALE_PROPERTY.set(mOverviewPanel,
                 QUICK_SWITCH.getOverviewScaleAndOffset(mLauncher)[0] * 0.85f);
         ADJACENT_PAGE_HORIZONTAL_OFFSET.set(mOverviewPanel, 1f);
-        Log.d(BAD_STATE, "QuickSwitchTouchController initCurrentAnimation setContentAlpha=1");
         mOverviewPanel.setContentAlpha(1);
 
         mCurrentAnimation = mLauncher.getStateManager()
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index ca7f633..c49848a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -236,7 +236,8 @@
         PendingAnimation pa;
         if (goingUp) {
             currentInterpolator = Interpolators.LINEAR;
-            pa = mRecentsView.createTaskDismissAnimation(mTaskBeingDragged,
+            pa = new PendingAnimation(maxDuration);
+            mRecentsView.createTaskDismissAnimation(pa, mTaskBeingDragged,
                     true /* animateTaskView */, true /* removeTask */, maxDuration,
                     false /* dismissingForSplitSelection*/);
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TwoButtonNavbarTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TwoButtonNavbarTouchController.java
index e2747df..9f2c1d4 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TwoButtonNavbarTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TwoButtonNavbarTouchController.java
@@ -24,13 +24,11 @@
 
 import android.animation.ValueAnimator;
 import android.os.SystemClock;
-import android.util.Log;
 import android.view.MotionEvent;
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.AbstractStateChangeTouchController;
 import com.android.launcher3.touch.SingleAxisSwipeDetector;
 import com.android.quickstep.SystemUiProxy;
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 76f7718..f3630c1 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -33,6 +33,7 @@
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_GESTURE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_LEFT;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_RIGHT;
+import static com.android.launcher3.uioverrides.QuickstepLauncher.ENABLE_PIP_KEEP_CLEAR_ALGORITHM;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK;
@@ -46,6 +47,9 @@
 import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_CANCELED;
 import static com.android.quickstep.GestureState.STATE_RECENTS_SCROLLING_FINISHED;
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.CANCEL_RECENTS_ANIMATION;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.FINISH_RECENTS_ANIMATION;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.ON_SETTLED_ON_END_TARGET;
 import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC;
 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
@@ -58,6 +62,7 @@
 import android.annotation.TargetApi;
 import android.app.Activity;
 import android.app.ActivityManager;
+import android.app.WindowConfiguration;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Matrix;
@@ -81,6 +86,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 
+import com.android.internal.util.LatencyTracker;
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
@@ -99,6 +105,7 @@
 import com.android.quickstep.BaseActivityInterface.AnimationFactory;
 import com.android.quickstep.GestureState.GestureEndTarget;
 import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
+import com.android.quickstep.util.ActiveGestureErrorDetector;
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
@@ -120,7 +127,6 @@
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
-import com.android.systemui.shared.system.LatencyTrackerCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
@@ -140,7 +146,7 @@
         RecentsAnimationCallbacks.RecentsAnimationListener {
     private static final String TAG = "AbsSwipeUpHandler";
 
-    private static final String[] STATE_NAMES = DEBUG_STATES ? new String[17] : null;
+    private static final ArrayList<String> STATE_NAMES = new ArrayList<>();
 
     protected final BaseActivityInterface<S, T> mActivityInterface;
     protected final InputConsumerProxy mInputConsumerProxy;
@@ -171,66 +177,68 @@
                 }
             };
 
-    private static int getFlagForIndex(int index, String name) {
+    private static int FLAG_COUNT = 0;
+    private static int getNextStateFlag(String name) {
         if (DEBUG_STATES) {
-            STATE_NAMES[index] = name;
+            STATE_NAMES.add(name);
         }
-        return 1 << index;
+        int index = 1 << FLAG_COUNT;
+        FLAG_COUNT++;
+        return index;
     }
 
     // Launcher UI related states
     protected static final int STATE_LAUNCHER_PRESENT =
-            getFlagForIndex(0, "STATE_LAUNCHER_PRESENT");
+            getNextStateFlag("STATE_LAUNCHER_PRESENT");
     protected static final int STATE_LAUNCHER_STARTED =
-            getFlagForIndex(1, "STATE_LAUNCHER_STARTED");
+            getNextStateFlag("STATE_LAUNCHER_STARTED");
     protected static final int STATE_LAUNCHER_DRAWN =
-            getFlagForIndex(2, "STATE_LAUNCHER_DRAWN");
+            getNextStateFlag("STATE_LAUNCHER_DRAWN");
     // Called when the Launcher has connected to the touch interaction service (and the taskbar
     // ui controller is initialized)
     protected static final int STATE_LAUNCHER_BIND_TO_SERVICE =
-            getFlagForIndex(3, "STATE_LAUNCHER_BIND_TO_SERVICE");
+            getNextStateFlag("STATE_LAUNCHER_BIND_TO_SERVICE");
 
     // Internal initialization states
     private static final int STATE_APP_CONTROLLER_RECEIVED =
-            getFlagForIndex(4, "STATE_APP_CONTROLLER_RECEIVED");
+            getNextStateFlag("STATE_APP_CONTROLLER_RECEIVED");
 
     // Interaction finish states
     private static final int STATE_SCALED_CONTROLLER_HOME =
-            getFlagForIndex(5, "STATE_SCALED_CONTROLLER_HOME");
+            getNextStateFlag("STATE_SCALED_CONTROLLER_HOME");
     private static final int STATE_SCALED_CONTROLLER_RECENTS =
-            getFlagForIndex(6, "STATE_SCALED_CONTROLLER_RECENTS");
+            getNextStateFlag("STATE_SCALED_CONTROLLER_RECENTS");
 
     protected static final int STATE_HANDLER_INVALIDATED =
-            getFlagForIndex(7, "STATE_HANDLER_INVALIDATED");
+            getNextStateFlag("STATE_HANDLER_INVALIDATED");
     private static final int STATE_GESTURE_STARTED =
-            getFlagForIndex(8, "STATE_GESTURE_STARTED");
+            getNextStateFlag("STATE_GESTURE_STARTED");
     private static final int STATE_GESTURE_CANCELLED =
-            getFlagForIndex(9, "STATE_GESTURE_CANCELLED");
+            getNextStateFlag("STATE_GESTURE_CANCELLED");
     private static final int STATE_GESTURE_COMPLETED =
-            getFlagForIndex(10, "STATE_GESTURE_COMPLETED");
+            getNextStateFlag("STATE_GESTURE_COMPLETED");
 
     private static final int STATE_CAPTURE_SCREENSHOT =
-            getFlagForIndex(11, "STATE_CAPTURE_SCREENSHOT");
+            getNextStateFlag("STATE_CAPTURE_SCREENSHOT");
     protected static final int STATE_SCREENSHOT_CAPTURED =
-            getFlagForIndex(12, "STATE_SCREENSHOT_CAPTURED");
+            getNextStateFlag("STATE_SCREENSHOT_CAPTURED");
     private static final int STATE_SCREENSHOT_VIEW_SHOWN =
-            getFlagForIndex(13, "STATE_SCREENSHOT_VIEW_SHOWN");
+            getNextStateFlag("STATE_SCREENSHOT_VIEW_SHOWN");
 
     private static final int STATE_RESUME_LAST_TASK =
-            getFlagForIndex(14, "STATE_RESUME_LAST_TASK");
+            getNextStateFlag("STATE_RESUME_LAST_TASK");
     private static final int STATE_START_NEW_TASK =
-            getFlagForIndex(15, "STATE_START_NEW_TASK");
+            getNextStateFlag("STATE_START_NEW_TASK");
     private static final int STATE_CURRENT_TASK_FINISHED =
-            getFlagForIndex(16, "STATE_CURRENT_TASK_FINISHED");
+            getNextStateFlag("STATE_CURRENT_TASK_FINISHED");
     private static final int STATE_FINISH_WITH_NO_END =
-            getFlagForIndex(17, "STATE_FINISH_WITH_NO_END");
+            getNextStateFlag("STATE_FINISH_WITH_NO_END");
 
     private static final int LAUNCHER_UI_STATES =
             STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED |
                     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 +306,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));
@@ -314,8 +325,29 @@
         initStateCallbacks();
     }
 
+    @Nullable
+    private static ActiveGestureErrorDetector.GestureEvent getTrackedEventForState(int stateFlag) {
+        if (stateFlag == STATE_GESTURE_STARTED) {
+            return ActiveGestureErrorDetector.GestureEvent.STATE_GESTURE_STARTED;
+        } else if (stateFlag == STATE_GESTURE_COMPLETED) {
+            return ActiveGestureErrorDetector.GestureEvent.STATE_GESTURE_COMPLETED;
+        } else if (stateFlag == STATE_GESTURE_CANCELLED) {
+            return ActiveGestureErrorDetector.GestureEvent.STATE_GESTURE_CANCELLED;
+        } else if (stateFlag == STATE_SCREENSHOT_CAPTURED) {
+            return ActiveGestureErrorDetector.GestureEvent.STATE_SCREENSHOT_CAPTURED;
+        } else if (stateFlag == STATE_CAPTURE_SCREENSHOT) {
+            return ActiveGestureErrorDetector.GestureEvent.STATE_CAPTURE_SCREENSHOT;
+        } else if (stateFlag == STATE_HANDLER_INVALIDATED) {
+            return ActiveGestureErrorDetector.GestureEvent.STATE_HANDLER_INVALIDATED;
+        } else if (stateFlag == STATE_LAUNCHER_DRAWN) {
+            return ActiveGestureErrorDetector.GestureEvent.STATE_LAUNCHER_DRAWN;
+        }
+        return null;
+    }
+
     private void initStateCallbacks() {
-        mStateCallback = new MultiStateCallback(STATE_NAMES);
+        mStateCallback = new MultiStateCallback(
+                STATE_NAMES.toArray(new String[0]), AbsSwipeUpHandler::getTrackedEventForState);
 
         mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED,
                 this::onLauncherPresentAndGestureStarted);
@@ -442,7 +474,7 @@
             });
 
         setupRecentsViewUi();
-        linkRecentsViewScroll();
+        mRecentsView.runOnPageScrollsInitialized(this::linkRecentsViewScroll);
         activity.runOnBindToTouchInteractionService(this::onLauncherBindToService);
 
         mActivity.registerActivityLifecycleCallbacks(mLifecycleCallbacks);
@@ -571,7 +603,7 @@
 
     protected void notifyGestureAnimationStartToRecents() {
         Task[] runningTasks;
-        if (mIsSwipeForStagedSplit) {
+        if (mIsSwipeForSplit) {
             int[] splitTaskIds = TopTaskTracker.INSTANCE.get(mContext).getRunningSplitTaskIds();
             runningTasks = mGestureState.getRunningTask().getPlaceholderTasks(splitTaskIds);
         } else {
@@ -589,8 +621,8 @@
 
         Object traceToken = TraceHelper.INSTANCE.beginSection("logToggleRecents",
                 TraceHelper.FLAG_IGNORE_BINDERS);
-        LatencyTrackerCompat.logToggleRecents(
-                mContext, (int) (mLauncherFrameDrawnTime - mTouchTimeMs));
+        LatencyTracker.getInstance(mContext).logAction(LatencyTracker.ACTION_TOGGLE_RECENTS,
+                (int) (mLauncherFrameDrawnTime - mTouchTimeMs));
         TraceHelper.INSTANCE.endSection(traceToken);
 
         // This method is only called when STATE_GESTURE_STARTED is set, so we can enable the
@@ -794,7 +826,6 @@
     public void onRecentsAnimationStart(RecentsAnimationController controller,
             RecentsAnimationTargets targets) {
         super.onRecentsAnimationStart(controller, targets);
-        ActiveGestureLog.INSTANCE.addLog("startRecentsAnimationCallback", targets.apps.length);
         mRemoteTargetHandles = mTargetGluer.assignTargetsForSplitScreen(mContext, targets);
         mRecentsAnimationController = controller;
         mRecentsAnimationTargets = targets;
@@ -837,17 +868,14 @@
 
     @Override
     public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
-        ActiveGestureLog.INSTANCE.addLog("cancelRecentsAnimation");
+        ActiveGestureLog.INSTANCE.addLog(
+                /* event= */ "cancelRecentsAnimation",
+                /* gestureEvent= */ CANCEL_RECENTS_ANIMATION);
         mActivityInitListener.unregister();
         // Cache the recents animation controller so we can defer its cleanup to after having
         // properly cleaned up the screenshot without accidentally using it.
         mDeferredCleanupRecentsAnimationController = mRecentsAnimationController;
         mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
-
-        if (mRecentsAnimationTargets != null) {
-            setDividerShown(true /* shown */, false /* immediate */);
-        }
-
         // Defer clearing the controller and the targets until after we've updated the state
         mRecentsAnimationController = null;
         mRecentsAnimationTargets = null;
@@ -862,6 +890,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 +906,7 @@
                     InteractionJankMonitorWrapper.begin(mRecentsView,
                             InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME);
 
-                    mRecentsView.post(() ->
-                            mRecentsView.getViewTreeObserver().removeOnDrawListener(this));
+                    rv.post(() -> rv.getViewTreeObserver().removeOnDrawListener(this));
                 }
             });
         }
@@ -1004,7 +1032,9 @@
                 }
                 break;
         }
-        ActiveGestureLog.INSTANCE.addLog("onSettledOnEndTarget " + endTarget);
+        ActiveGestureLog.INSTANCE.addLog(
+                /* event= */ "onSettledOnEndTarget " + endTarget,
+                /* gestureEvent= */ ON_SETTLED_ON_END_TARGET);
     }
 
     /** @return Whether this was the task we were waiting to appear, and thus handled it. */
@@ -1021,77 +1051,90 @@
         return false;
     }
 
-    private GestureEndTarget calculateEndTarget(PointF velocity, float endVelocity,
-            boolean isFlingY, boolean isCancel) {
+    private GestureEndTarget calculateEndTarget(
+            PointF velocity, float endVelocity, boolean isFlingY, boolean isCancel) {
+
         if (mGestureState.isHandlingAtomicEvent()) {
-            // Button mode, this is only used to go to recents
+            // Button mode, this is only used to go to recents.
             return RECENTS;
         }
-        final GestureEndTarget endTarget;
-        final boolean goingToNewTask;
-        if (mRecentsView != null) {
-            if (!hasTargets()) {
-                // If there are no running tasks, then we can assume that this is a continuation of
-                // the last gesture, but after the recents animation has finished
-                goingToNewTask = true;
-            } else {
-                final int runningTaskIndex = mRecentsView.getRunningTaskIndex();
-                final int taskToLaunch = mRecentsView.getNextPage();
-                goingToNewTask = runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex;
-            }
-        } else {
-            goingToNewTask = false;
-        }
-        final boolean reachedOverviewThreshold = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW;
-        final boolean isFlingX = Math.abs(velocity.x) > mContext.getResources()
-                .getDimension(R.dimen.quickstep_fling_threshold_speed);
-        if (!isFlingY) {
-            if (isCancel) {
-                endTarget = LAST_TASK;
-            } else if (mDeviceState.isFullyGesturalNavMode()) {
-                if (goingToNewTask && isFlingX) {
-                    // Flinging towards new task takes precedence over mIsMotionPaused (which only
-                    // checks y-velocity).
-                    endTarget = NEW_TASK;
-                } else if (mIsMotionPaused) {
-                    endTarget = RECENTS;
-                } else if (goingToNewTask) {
-                    endTarget = NEW_TASK;
-                } else {
-                    endTarget = !reachedOverviewThreshold ? LAST_TASK : HOME;
-                }
-            } else {
-                endTarget = reachedOverviewThreshold && mGestureStarted
-                        ? RECENTS
-                        : goingToNewTask
-                                ? NEW_TASK
-                                : LAST_TASK;
-            }
-        } else {
-            // If swiping at a diagonal, base end target on the faster velocity.
-            boolean isSwipeUp = endVelocity < 0;
-            boolean willGoToNewTaskOnSwipeUp =
-                    goingToNewTask && Math.abs(velocity.x) > Math.abs(endVelocity);
 
-            if (mDeviceState.isFullyGesturalNavMode() && isSwipeUp && !willGoToNewTaskOnSwipeUp) {
-                endTarget = HOME;
-            } else if (mDeviceState.isFullyGesturalNavMode() && isSwipeUp) {
-                // If swiping at a diagonal, base end target on the faster velocity.
-                endTarget = NEW_TASK;
-            } else if (isSwipeUp) {
-                endTarget = !reachedOverviewThreshold && willGoToNewTaskOnSwipeUp
-                        ? NEW_TASK : RECENTS;
-            } else {
-                endTarget = goingToNewTask ? NEW_TASK : LAST_TASK;
-            }
+        GestureEndTarget endTarget;
+        if (isCancel) {
+            endTarget = LAST_TASK;
+        } else if (isFlingY) {
+            endTarget = calculateEndTargetForFlingY(velocity, endVelocity);
+        } else {
+            endTarget = calculateEndTargetForNonFling(velocity);
         }
 
-        if (mDeviceState.isOverviewDisabled() && (endTarget == RECENTS || endTarget == LAST_TASK)) {
+        if (mDeviceState.isOverviewDisabled() && endTarget == RECENTS) {
             return LAST_TASK;
         }
+
         return endTarget;
     }
 
+    private GestureEndTarget calculateEndTargetForFlingY(PointF velocity, float endVelocity) {
+        // If swiping at a diagonal, base end target on the faster velocity direction.
+        final boolean willGoToNewTask =
+                isScrollingToNewTask() && Math.abs(velocity.x) > Math.abs(endVelocity);
+        final boolean isSwipeUp = endVelocity < 0;
+        if (!isSwipeUp) {
+            final boolean isCenteredOnNewTask =
+                    mRecentsView.getDestinationPage() != mRecentsView.getRunningTaskIndex();
+            return willGoToNewTask || isCenteredOnNewTask ? NEW_TASK : LAST_TASK;
+        }
+
+        if (!mDeviceState.isFullyGesturalNavMode()) {
+            return (!hasReachedOverviewThreshold() && willGoToNewTask) ? NEW_TASK : RECENTS;
+        }
+        return willGoToNewTask ? NEW_TASK : HOME;
+    }
+
+    private GestureEndTarget calculateEndTargetForNonFling(PointF velocity) {
+        final boolean isScrollingToNewTask = isScrollingToNewTask();
+        final boolean reachedOverviewThreshold = hasReachedOverviewThreshold();
+        if (!mDeviceState.isFullyGesturalNavMode()) {
+            return reachedOverviewThreshold && mGestureStarted
+                    ? RECENTS
+                    : (isScrollingToNewTask ? NEW_TASK : LAST_TASK);
+        }
+
+        // Fully gestural mode.
+        final boolean isFlingX = Math.abs(velocity.x) > mContext.getResources()
+                .getDimension(R.dimen.quickstep_fling_threshold_speed);
+        if (isScrollingToNewTask && isFlingX) {
+            // Flinging towards new task takes precedence over mIsMotionPaused (which only
+            // checks y-velocity).
+            return NEW_TASK;
+        } else if (mIsMotionPaused) {
+            return RECENTS;
+        } else if (isScrollingToNewTask) {
+            return NEW_TASK;
+        } else if (reachedOverviewThreshold) {
+            return HOME;
+        }
+        return LAST_TASK;
+    }
+
+    private boolean isScrollingToNewTask() {
+        if (mRecentsView == null) {
+            return false;
+        }
+        if (!hasTargets()) {
+            // If there are no running tasks, then we can assume that this is a continuation of
+            // the last gesture, but after the recents animation has finished.
+            return true;
+        }
+        int runningTaskIndex = mRecentsView.getRunningTaskIndex();
+        return runningTaskIndex >= 0 && mRecentsView.getNextPage() != runningTaskIndex;
+    }
+
+    private boolean hasReachedOverviewThreshold() {
+        return mCurrentShift.value > MIN_PROGRESS_FOR_OVERVIEW;
+    }
+
     @UiThread
     private void handleNormalGestureEnd(float endVelocity, boolean isFling, PointF velocity,
             boolean isCancel) {
@@ -1137,7 +1180,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);
@@ -1167,11 +1212,16 @@
                     duration = Math.max(duration, mRecentsView.getScroller().getDuration());
                 }
             }
+        } else if (endTarget == LAST_TASK && mRecentsView != null
+                && mRecentsView.getNextPage() != mRecentsView.getRunningTaskIndex()) {
+            mRecentsView.snapToPage(mRecentsView.getRunningTaskIndex(), Math.toIntExact(duration));
         }
 
         // Let RecentsView handle the scrolling to the task, which we launch in startNewTask()
         // or resumeLastTask().
         if (mRecentsView != null) {
+            ActiveGestureLog.INSTANCE.trackEvent(ActiveGestureErrorDetector.GestureEvent
+                    .SET_ON_PAGE_TRANSITION_END_CALLBACK);
             mRecentsView.setOnPageTransitionEndCallback(
                     () -> mGestureState.setState(STATE_RECENTS_SCROLLING_FINISHED));
         } else {
@@ -1280,15 +1330,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(
@@ -1319,7 +1372,8 @@
                 if (windowAnimation == null) {
                     continue;
                 }
-                windowAnimation.start(mContext, velocityPxPerMs);
+                DeviceProfile dp = mActivity == null ? null : mActivity.getDeviceProfile();
+                windowAnimation.start(mContext, dp, velocityPxPerMs);
                 mRunningWindowAnim[i] = RunningWindowAnim.wrap(windowAnimation);
             }
             homeAnimFactory.setSwipeVelocity(velocityPxPerMs.y);
@@ -1390,9 +1444,7 @@
         }
     }
 
-    /**
-     * TODO(b/195473090) handle multiple task simulators (if needed) for PIP
-     */
+    @Nullable
     private SwipePipToHomeAnimator createWindowAnimationToPip(HomeAnimationFactory homeAnimFactory,
             RemoteAnimationTargetCompat runningTaskTarget, float startProgress) {
         // Directly animate the app to PiP (picture-in-picture) mode
@@ -1411,12 +1463,27 @@
         homeToWindowPositionMap.invert(windowToHomePositionMap);
         windowToHomePositionMap.mapRect(startRect);
 
+        final Rect hotseatKeepClearArea = getKeepClearAreaForHotseat();
         final Rect destinationBounds = SystemUiProxy.INSTANCE.get(mContext)
                 .startSwipePipToHome(taskInfo.topActivity,
                         taskInfo.topActivityInfo,
                         runningTaskTarget.taskInfo.pictureInPictureParams,
                         homeRotation,
-                        mDp.hotseatBarSizePx);
+                        hotseatKeepClearArea);
+        if (destinationBounds == null) {
+            // No destination bounds returned from SystemUI, bail early.
+            return null;
+        }
+        final Rect appBounds = new Rect();
+        final WindowConfiguration winConfig = taskInfo.configuration.windowConfiguration;
+        // Adjust the appBounds for TaskBar by using the calculated window crop Rect
+        // from TaskViewSimulator and fallback to the bounds in TaskInfo when it's originated
+        // from windowing modes other than full-screen.
+        if (winConfig.getWindowingMode() == WindowConfiguration.WINDOWING_MODE_FULLSCREEN) {
+            mRemoteTargetHandles[0].getTaskViewSimulator().getCurrentCropRect().round(appBounds);
+        } else {
+            appBounds.set(winConfig.getBounds());
+        }
         final SwipePipToHomeAnimator.Builder builder = new SwipePipToHomeAnimator.Builder()
                 .setContext(mContext)
                 .setTaskId(runningTaskTarget.taskId)
@@ -1424,7 +1491,7 @@
                 .setLeash(runningTaskTarget.leash)
                 .setSourceRectHint(
                         runningTaskTarget.taskInfo.pictureInPictureParams.getSourceRectHint())
-                .setAppBounds(taskInfo.configuration.windowConfiguration.getBounds())
+                .setAppBounds(appBounds)
                 .setHomeToWindowPositionMap(homeToWindowPositionMap)
                 .setStartBounds(startRect)
                 .setDestinationBounds(destinationBounds)
@@ -1469,6 +1536,35 @@
         return swipePipToHomeAnimator;
     }
 
+    private Rect getKeepClearAreaForHotseat() {
+        Rect keepClearArea;
+        if (!ENABLE_PIP_KEEP_CLEAR_ALGORITHM) {
+            // make the height equal to hotseatBarSizePx only
+            keepClearArea = new Rect(0, 0, 0, mDp.hotseatBarSizePx);
+            return keepClearArea;
+        }
+        // the keep clear area in global screen coordinates, in pixels
+        if (mDp.isPhone) {
+            if (mDp.isSeascape()) {
+                // in seascape the Hotseat is on the left edge of the screen
+                keepClearArea = new Rect(0, 0, mDp.hotseatBarSizePx, mDp.heightPx);
+            } else if (mDp.isLandscape) {
+                // in landscape the Hotseat is on the right edge of the screen
+                keepClearArea = new Rect(mDp.widthPx - mDp.hotseatBarSizePx, 0,
+                        mDp.widthPx, mDp.heightPx);
+            } else {
+                // in portrait mode the Hotseat is at the bottom of the screen
+                keepClearArea = new Rect(0, mDp.heightPx - mDp.hotseatBarSizePx,
+                        mDp.widthPx, mDp.heightPx);
+            }
+        } else {
+            // large screens have Hotseat always at the bottom of the screen
+            keepClearArea = new Rect(0, mDp.heightPx - mDp.hotseatBarSizePx,
+                    mDp.widthPx, mDp.heightPx);
+        }
+        return keepClearArea;
+    }
+
     private void startInterceptingTouchesForGesture() {
         if (mRecentsAnimationController == null) {
             return;
@@ -1556,7 +1652,10 @@
     private void resumeLastTask() {
         if (mRecentsAnimationController != null) {
             mRecentsAnimationController.finish(false /* toRecents */, null);
-            ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false);
+            ActiveGestureLog.INSTANCE.addLog(
+                    /* event= */ "finishRecentsAnimation",
+                    /* extras= */ false,
+                    /* gestureEvent= */ FINISH_RECENTS_ANIMATION);
         }
         doLogGesture(LAST_TASK, null);
         reset();
@@ -1605,13 +1704,17 @@
      * handler (in case of quick switch).
      */
     private void cancelCurrentAnimation() {
+        ActiveGestureLog.INSTANCE.addLog(
+                "AbsSwipeUpHandler.cancelCurrentAnimation",
+                ActiveGestureErrorDetector.GestureEvent.CANCEL_CURRENT_ANIMATION);
         mCanceled = true;
         mCurrentShift.cancelAnimation();
 
         // Cleanup when switching handlers
         mInputConsumerProxy.unregisterCallback();
         mActivityInitListener.unregister();
-        ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mActivityRestartListener);
+        TaskStackChangeListeners.getInstance().unregisterTaskStackListener(
+                mActivityRestartListener);
         mTaskSnapshot = null;
     }
 
@@ -1675,10 +1778,6 @@
         boolean wasVisible = mWasLauncherAlreadyVisible || mGestureStarted;
         mActivityInterface.onTransitionCancelled(wasVisible, mGestureState.getEndTarget());
 
-        if (mRecentsAnimationTargets != null) {
-            setDividerShown(true /* shown */, true /* immediate */);
-        }
-
         // Leave the pending invisible flag, as it may be used by wallpaper open animation.
         if (mActivity != null) {
             mActivity.clearForceInvisibleFlag(INVISIBLE_BY_STATE_HANDLER);
@@ -1759,7 +1858,10 @@
             mRecentsAnimationController.finish(true /* toRecents */,
                     () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
         }
-        ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true);
+        ActiveGestureLog.INSTANCE.addLog(
+                /* event= */ "finishRecentsAnimation",
+                /* extras= */ true,
+                /* gestureEvent= */ FINISH_RECENTS_ANIMATION);
     }
 
     private void finishCurrentTransitionToHome() {
@@ -1771,7 +1873,10 @@
             finishRecentsControllerToHome(
                     () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
         }
-        ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true);
+        ActiveGestureLog.INSTANCE.addLog(
+                /* event= */ "finishRecentsAnimation",
+                /* extras= */ true,
+                /* gestureEvent= */ FINISH_RECENTS_ANIMATION);
         doLogGesture(HOME, mRecentsView == null ? null : mRecentsView.getCurrentPageTaskView());
     }
 
@@ -1792,12 +1897,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,
@@ -1957,8 +2063,10 @@
             if (handleTaskAppeared(appearedTaskTargets)) {
                 mRecentsAnimationController.finish(false /* toRecents */,
                         null /* onFinishComplete */);
-                mActivityInterface.onLaunchTaskSuccess();
-                ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false);
+                ActiveGestureLog.INSTANCE.addLog(
+                        /* event= */ "finishRecentsAnimation",
+                        /* extras= */ false,
+                        /* gestureEvent= */ FINISH_RECENTS_ANIMATION);
             }
         }
     }
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..226b173 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -55,14 +55,11 @@
 import com.android.launcher3.taskbar.TaskbarUIController;
 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.util.NavigationMode;
 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;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
@@ -186,14 +183,6 @@
 
     public abstract void onLaunchTaskFailed();
 
-    public void onLaunchTaskSuccess() {
-        ACTIVITY_TYPE activity = getCreatedActivity();
-        if (activity == null) {
-            return;
-        }
-        activity.getStateManager().moveToRestState();
-    }
-
     /**
      * Closes any overlays.
      */
@@ -258,7 +247,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 +259,20 @@
         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 (dp.isTablet) {
+            out.y -= dp.taskbarSize;
         }
     }
 
@@ -341,7 +303,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/FallbackActivityInterface.java b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
index ba61574..466abbe 100644
--- a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
@@ -16,7 +16,7 @@
 package com.android.quickstep;
 
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.launcher3.util.DisplayController.NavigationMode.NO_BUTTON;
+import static com.android.launcher3.util.NavigationMode.NO_BUTTON;
 import static com.android.quickstep.fallback.RecentsState.BACKGROUND_APP;
 import static com.android.quickstep.fallback.RecentsState.DEFAULT;
 import static com.android.quickstep.fallback.RecentsState.HOME;
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/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index 3b52e91..bcd9687 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -19,6 +19,10 @@
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET_HOME;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET_LAST_TASK;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET_NEW_TASK;
 
 import android.annotation.Nullable;
 import android.annotation.TargetApi;
@@ -30,6 +34,7 @@
 import com.android.launcher3.tracing.GestureStateProto;
 import com.android.launcher3.tracing.SwipeHandlerProto;
 import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
+import com.android.quickstep.util.ActiveGestureErrorDetector;
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -38,6 +43,7 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -82,11 +88,11 @@
 
     private static final String TAG = "GestureState";
 
-    private static final ArrayList<String> STATE_NAMES = new ArrayList<>();
+    private static final List<String> STATE_NAMES = new ArrayList<>();
     public static final GestureState DEFAULT_STATE = new GestureState();
 
     private static int FLAG_COUNT = 0;
-    private static int getFlagForIndex(String name) {
+    private static int getNextStateFlag(String name) {
         if (DEBUG_STATES) {
             STATE_NAMES.add(name);
         }
@@ -97,36 +103,36 @@
 
     // Called when the end target as been set
     public static final int STATE_END_TARGET_SET =
-            getFlagForIndex("STATE_END_TARGET_SET");
+            getNextStateFlag("STATE_END_TARGET_SET");
 
     // Called when the end target animation has finished
     public static final int STATE_END_TARGET_ANIMATION_FINISHED =
-            getFlagForIndex("STATE_END_TARGET_ANIMATION_FINISHED");
+            getNextStateFlag("STATE_END_TARGET_ANIMATION_FINISHED");
 
     // Called when the recents animation has been requested to start
     public static final int STATE_RECENTS_ANIMATION_INITIALIZED =
-            getFlagForIndex("STATE_RECENTS_ANIMATION_INITIALIZED");
+            getNextStateFlag("STATE_RECENTS_ANIMATION_INITIALIZED");
 
     // Called when the recents animation is started and the TaskAnimationManager has been updated
     // with the controller and targets
     public static final int STATE_RECENTS_ANIMATION_STARTED =
-            getFlagForIndex("STATE_RECENTS_ANIMATION_STARTED");
+            getNextStateFlag("STATE_RECENTS_ANIMATION_STARTED");
 
     // Called when the recents animation is canceled
     public static final int STATE_RECENTS_ANIMATION_CANCELED =
-            getFlagForIndex("STATE_RECENTS_ANIMATION_CANCELED");
+            getNextStateFlag("STATE_RECENTS_ANIMATION_CANCELED");
 
     // Called when the recents animation finishes
     public static final int STATE_RECENTS_ANIMATION_FINISHED =
-            getFlagForIndex("STATE_RECENTS_ANIMATION_FINISHED");
+            getNextStateFlag("STATE_RECENTS_ANIMATION_FINISHED");
 
     // Always called when the recents animation ends (regardless of cancel or finish)
     public static final int STATE_RECENTS_ANIMATION_ENDED =
-            getFlagForIndex("STATE_RECENTS_ANIMATION_ENDED");
+            getNextStateFlag("STATE_RECENTS_ANIMATION_ENDED");
 
     // Called when RecentsView stops scrolling and settles on a TaskView.
     public static final int STATE_RECENTS_SCROLLING_FINISHED =
-            getFlagForIndex("STATE_RECENTS_SCROLLING_FINISHED");
+            getNextStateFlag("STATE_RECENTS_SCROLLING_FINISHED");
 
     // Needed to interact with the current activity
     private final Intent mHomeIntent;
@@ -152,7 +158,8 @@
         mHomeIntent = componentObserver.getHomeIntent();
         mOverviewIntent = componentObserver.getOverviewIntent();
         mActivityInterface = componentObserver.getActivityInterface();
-        mStateCallback = new MultiStateCallback(STATE_NAMES.toArray(new String[0]));
+        mStateCallback = new MultiStateCallback(
+                STATE_NAMES.toArray(new String[0]), GestureState::getTrackedEventForState);
         mGestureId = gestureId;
     }
 
@@ -174,10 +181,23 @@
         mHomeIntent = new Intent();
         mOverviewIntent = new Intent();
         mActivityInterface = null;
-        mStateCallback = new MultiStateCallback(STATE_NAMES.toArray(new String[0]));
+        mStateCallback = new MultiStateCallback(
+                STATE_NAMES.toArray(new String[0]), GestureState::getTrackedEventForState);
         mGestureId = -1;
     }
 
+    @Nullable
+    private static ActiveGestureErrorDetector.GestureEvent getTrackedEventForState(int stateFlag) {
+        if (stateFlag == STATE_END_TARGET_ANIMATION_FINISHED) {
+            return ActiveGestureErrorDetector.GestureEvent.STATE_END_TARGET_ANIMATION_FINISHED;
+        } else if (stateFlag == STATE_RECENTS_SCROLLING_FINISHED) {
+            return ActiveGestureErrorDetector.GestureEvent.STATE_RECENTS_SCROLLING_FINISHED;
+        } else if (stateFlag == STATE_RECENTS_ANIMATION_CANCELED) {
+            return ActiveGestureErrorDetector.GestureEvent.STATE_RECENTS_ANIMATION_CANCELED;
+        }
+        return null;
+    }
+
     /**
      * @return whether the gesture state has the provided {@param stateMask} flags set.
      */
@@ -310,7 +330,23 @@
     public void setEndTarget(GestureEndTarget target, boolean isAtomic) {
         mEndTarget = target;
         mStateCallback.setState(STATE_END_TARGET_SET);
-        ActiveGestureLog.INSTANCE.addLog("setEndTarget " + mEndTarget);
+        ActiveGestureLog.INSTANCE.addLog(
+                /* event= */ "setEndTarget " + mEndTarget,
+                /* gestureEvent= */ SET_END_TARGET);
+        switch (mEndTarget) {
+            case HOME:
+                ActiveGestureLog.INSTANCE.trackEvent(SET_END_TARGET_HOME);
+                break;
+            case NEW_TASK:
+                ActiveGestureLog.INSTANCE.trackEvent(SET_END_TARGET_NEW_TASK);
+                break;
+            case LAST_TASK:
+                ActiveGestureLog.INSTANCE.trackEvent(SET_END_TARGET_LAST_TASK);
+                break;
+            case RECENTS:
+            default:
+                // No-Op
+        }
         if (isAtomic) {
             mStateCallback.setState(STATE_END_TARGET_ANIMATION_FINISHED);
         }
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/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index c13b95f..1127e2c 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -33,7 +33,6 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 
-import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherInitListener;
@@ -44,8 +43,9 @@
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.taskbar.LauncherTaskbarUIController;
 import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.DisplayController.NavigationMode;
+import com.android.launcher3.util.NavigationMode;
 import com.android.quickstep.GestureState.GestureEndTarget;
 import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
@@ -61,7 +61,7 @@
  * {@link BaseActivityInterface} for the in-launcher recents.
  */
 public final class LauncherActivityInterface extends
-        BaseActivityInterface<LauncherState, BaseQuickstepLauncher> {
+        BaseActivityInterface<LauncherState, QuickstepLauncher> {
 
     public static final LauncherActivityInterface INSTANCE = new LauncherActivityInterface();
 
@@ -122,7 +122,7 @@
         notifyRecentsOfOrientation(deviceState.getRotationTouchHelper());
         DefaultAnimationFactory factory = new DefaultAnimationFactory(callback) {
             @Override
-            protected void createBackgroundToOverviewAnim(BaseQuickstepLauncher activity,
+            protected void createBackgroundToOverviewAnim(QuickstepLauncher activity,
                     PendingAnimation pa) {
                 super.createBackgroundToOverviewAnim(activity, pa);
 
@@ -135,7 +135,7 @@
             }
         };
 
-        BaseQuickstepLauncher launcher = factory.initBackgroundStateUI();
+        QuickstepLauncher launcher = factory.initBackgroundStateUI();
         // Since all apps is not visible, we can safely reset the scroll position.
         // This ensures then the next swipe up to all-apps starts from scroll 0.
         launcher.getAppsView().reset(false /* animate */);
@@ -159,14 +159,14 @@
 
     @Nullable
     @Override
-    public BaseQuickstepLauncher getCreatedActivity() {
-        return BaseQuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity();
+    public QuickstepLauncher getCreatedActivity() {
+        return QuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity();
     }
 
     @Nullable
     @Override
     public DepthController getDepthController() {
-        BaseQuickstepLauncher launcher = getCreatedActivity();
+        QuickstepLauncher launcher = getCreatedActivity();
         if (launcher == null) {
             return null;
         }
@@ -176,7 +176,7 @@
     @Nullable
     @Override
     public LauncherTaskbarUIController getTaskbarController() {
-        BaseQuickstepLauncher launcher = getCreatedActivity();
+        QuickstepLauncher launcher = getCreatedActivity();
         if (launcher == null) {
             return null;
         }
@@ -318,7 +318,7 @@
     }
 
     @Override
-    protected int getOverviewScrimColorForState(BaseQuickstepLauncher launcher,
+    protected int getOverviewScrimColorForState(QuickstepLauncher launcher,
             LauncherState state) {
         return state.getWorkspaceScrimColor(launcher);
     }
diff --git a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
index fd9f922..7a281dd 100644
--- a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
+++ b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
@@ -39,10 +39,10 @@
 import android.window.IOnBackInvokedCallback;
 
 import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.QuickstepTransitionManager;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.quickstep.util.RectFSpringAnim;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -75,7 +75,7 @@
     private final RectF mCancelRect = new RectF();
     /** The current window position. */
     private final RectF mCurrentRect = new RectF();
-    private final BaseQuickstepLauncher mLauncher;
+    private final QuickstepLauncher mLauncher;
     private final int mWindowScaleMarginX;
     /** Max window translation in the Y axis. */
     private final int mWindowMaxDeltaY;
@@ -93,7 +93,7 @@
     private IOnBackInvokedCallback mBackCallback;
 
     public LauncherBackAnimationController(
-            BaseQuickstepLauncher launcher,
+            QuickstepLauncher launcher,
             QuickstepTransitionManager quickstepTransitionManager) {
         mLauncher = launcher;
         mQuickstepTransitionManager = quickstepTransitionManager;
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index 50d1244..36ca993 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -35,10 +35,10 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.util.ObjectWrapper;
 import com.android.launcher3.views.FloatingIconView;
 import com.android.launcher3.views.FloatingView;
@@ -57,7 +57,7 @@
  * Temporary class to allow easier refactoring
  */
 public class LauncherSwipeHandlerV2 extends
-        AbsSwipeUpHandler<BaseQuickstepLauncher, RecentsView, LauncherState> {
+        AbsSwipeUpHandler<QuickstepLauncher, RecentsView, LauncherState> {
 
     public LauncherSwipeHandlerV2(Context context, RecentsAnimationDeviceState deviceState,
             TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs,
@@ -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/MultiStateCallback.java b/quickstep/src/com/android/quickstep/MultiStateCallback.java
index b3875ae..a68bea2 100644
--- a/quickstep/src/com/android/quickstep/MultiStateCallback.java
+++ b/quickstep/src/com/android/quickstep/MultiStateCallback.java
@@ -22,7 +22,12 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.config.FeatureFlags;
+import com.android.quickstep.util.ActiveGestureErrorDetector;
+import com.android.quickstep.util.ActiveGestureLog;
 
 import java.util.ArrayList;
 import java.util.LinkedList;
@@ -41,17 +46,29 @@
     private final SparseArray<ArrayList<Consumer<Boolean>>> mStateChangeListeners =
             new SparseArray<>();
 
+    @NonNull private final TrackedEventsMapper mTrackedEventsMapper;
+
     private final String[] mStateNames;
 
     private int mState = 0;
 
     public MultiStateCallback(String[] stateNames) {
+        this(stateNames, stateFlag -> null);
+    }
+
+    public MultiStateCallback(
+            String[] stateNames,
+            @NonNull TrackedEventsMapper trackedEventsMapper) {
         mStateNames = DEBUG_STATES ? stateNames : null;
+        mTrackedEventsMapper = trackedEventsMapper;
     }
 
     /**
      * Adds the provided state flags to the global state on the UI thread and executes any callbacks
      * as a result.
+     *
+     * Also tracks the provided gesture events for error detection. Each provided event must be
+     * associated with one provided state flag.
      */
     public void setStateOnUiThread(int stateFlag) {
         if (Looper.myLooper() == Looper.getMainLooper()) {
@@ -69,7 +86,9 @@
             Log.d(TAG, "[" + System.identityHashCode(this) + "] Adding "
                     + convertToFlagNames(stateFlag) + " to " + convertToFlagNames(mState));
         }
-
+        if (FeatureFlags.ENABLE_GESTURE_ERROR_DETECTION.get()) {
+            trackGestureEvents(stateFlag);
+        }
         final int oldState = mState;
         mState = mState | stateFlag;
 
@@ -87,6 +106,26 @@
         notifyStateChangeListeners(oldState);
     }
 
+    private void trackGestureEvents(int stateFlags) {
+        for (int index = 0; (stateFlags >> index) != 0; index++) {
+            if ((stateFlags & (1 << index)) == 0) {
+                continue;
+            }
+            ActiveGestureErrorDetector.GestureEvent gestureEvent =
+                    mTrackedEventsMapper.getTrackedEventForState(1 << index);
+            if (gestureEvent == null) {
+                continue;
+            }
+            if (gestureEvent.mLogEvent && gestureEvent.mTrackEvent) {
+                ActiveGestureLog.INSTANCE.addLog(gestureEvent.name(), gestureEvent);
+            } else if (gestureEvent.mLogEvent) {
+                ActiveGestureLog.INSTANCE.addLog(gestureEvent.name());
+            } else if (gestureEvent.mTrackEvent) {
+                ActiveGestureLog.INSTANCE.trackEvent(gestureEvent);
+            }
+        }
+    }
+
     /**
      * Adds the provided state flags to the global state and executes any change handlers
      * as a result.
@@ -174,4 +213,7 @@
         return joiner.toString();
     }
 
+    public interface TrackedEventsMapper {
+        @Nullable ActiveGestureErrorDetector.GestureEvent getTrackedEventForState(int stateflag);
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
index 895cf89..1b05fd2 100644
--- a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
+++ b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
@@ -32,9 +32,9 @@
 import android.view.Surface;
 
 import com.android.launcher3.R;
-import com.android.launcher3.ResourceUtils;
+import com.android.launcher3.testing.shared.ResourceUtils;
 import com.android.launcher3.util.DisplayController.Info;
-import com.android.launcher3.util.DisplayController.NavigationMode;
+import com.android.launcher3.util.NavigationMode;
 import com.android.launcher3.util.window.CachedDisplayInfo;
 
 import java.io.PrintWriter;
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index dffdc5a..875b72c 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -144,7 +144,7 @@
         RunnableList callbackList = null;
         if (taskView != null) {
             taskView.setEndQuickswitchCuj(true);
-            callbackList = taskView.launchTaskAnimated();
+            callbackList = taskView.launchTasks();
         }
 
         if (callbackList != null) {
@@ -193,7 +193,20 @@
             }
         }
 
-        if (activityInterface.switchToRecentsIfVisible(() -> scheduleNextTask(cmd))) {
+        final Runnable completeCallback = () -> {
+            if (cmd.type == TYPE_SHOW_NEXT_FOCUS) {
+                RecentsView rv = activityInterface.getVisibleRecentsView();
+                // When the overview is launched via alt tab (cmd type is TYPE_SHOW_NEXT_FOCUS),
+                // the touch mode somehow is not change to false by the Android framework.
+                // The subsequent tab to go through tasks in overview can only be dispatched to
+                // focuses views, while focus can only be requested in
+                // {@link View#requestFocusNoSearch(int, Rect)} when touch mode is false. To note,
+                // here we launch overview from home.
+                rv.getViewRootImpl().touchModeChanged(false);
+            }
+            scheduleNextTask(cmd);
+        };
+        if (activityInterface.switchToRecentsIfVisible(completeCallback)) {
             // If successfully switched, wait until animation finishes
             return false;
         }
@@ -227,14 +240,21 @@
                 interactionHandler.onGestureCancelled();
                 cmd.removeListener(this);
 
-                RecentsView createdRecents =
-                        activityInterface.getCreatedActivity().getOverviewPanel();
+                T createdActivity = activityInterface.getCreatedActivity();
+                if (createdActivity == null) {
+                    return;
+                }
+                RecentsView createdRecents = createdActivity.getOverviewPanel();
                 if (createdRecents != null) {
                     createdRecents.onRecentsAnimationComplete();
                 }
             }
         };
 
+        RecentsView<?, ?> visibleRecentsView = activityInterface.getVisibleRecentsView();
+        if (visibleRecentsView != null) {
+            visibleRecentsView.moveFocusedTaskToFront();
+        }
         if (mTaskAnimationManager.isRecentsAnimationRunning()) {
             cmd.mActiveCallbacks = mTaskAnimationManager.continueRecentsAnimation(gestureState);
             cmd.mActiveCallbacks.addListener(interactionHandler);
@@ -264,6 +284,13 @@
             RecentsView rv =
                     mOverviewComponentObserver.getActivityInterface().getVisibleRecentsView();
             if (rv != null) {
+                // When the overview is launched via alt tab (cmd type is TYPE_SHOW_NEXT_FOCUS),
+                // the touch mode somehow is not change to false by the Android framework.
+                // The subsequent tab to go through tasks in overview can only be dispatched to
+                // focuses views, while focus can only be requested in
+                // {@link View#requestFocusNoSearch(int, Rect)} when touch mode is false. To note,
+                // here we launch overview with live tile.
+                rv.getViewRootImpl().touchModeChanged(false);
                 // Ensure that recents view has focus so that it receives the followup key inputs
                 TaskView taskView = rv.getNextTaskView();
                 if (taskView == null) {
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/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 528fb97..b7cdecd 100644
--- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -1,16 +1,25 @@
 package com.android.quickstep;
 
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
 import android.app.Activity;
 import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.Rect;
 import android.os.Bundle;
 
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.R;
 import com.android.launcher3.testing.TestInformationHandler;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.quickstep.util.LayoutUtils;
+import com.android.quickstep.util.TISBindHelper;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
 
 public class QuickstepTestInformationHandler extends TestInformationHandler {
 
@@ -72,6 +81,37 @@
                         TestProtocol.REQUEST_HAS_TIS, true);
                 return response;
             }
+
+            case TestProtocol.REQUEST_ENABLE_MANUAL_TASKBAR_STASHING:
+                runOnTISBinder(tisBinder -> {
+                    enableManualTaskbarStashing(tisBinder, true);
+                });
+                return response;
+
+            case TestProtocol.REQUEST_DISABLE_MANUAL_TASKBAR_STASHING:
+                runOnTISBinder(tisBinder -> {
+                    enableManualTaskbarStashing(tisBinder, false);
+                });
+                return response;
+
+            case TestProtocol.REQUEST_UNSTASH_TASKBAR_IF_STASHED:
+                runOnTISBinder(tisBinder -> {
+                    enableManualTaskbarStashing(tisBinder, true);
+
+                    // Allow null-pointer to catch illegal states.
+                    tisBinder.getTaskbarManager().getCurrentActivityContext()
+                            .unstashTaskbarIfStashed();
+
+                    enableManualTaskbarStashing(tisBinder, false);
+                });
+                return response;
+
+            case TestProtocol.REQUEST_STASHED_TASKBAR_HEIGHT: {
+                final Resources resources = mContext.getResources();
+                response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
+                        resources.getDimensionPixelSize(R.dimen.taskbar_stashed_size));
+                return response;
+            }
         }
 
         return super.call(method, arg, extras);
@@ -93,4 +133,30 @@
     protected boolean isLauncherInitialized() {
         return super.isLauncherInitialized() && TouchInteractionService.isInitialized();
     }
+
+    private void enableManualTaskbarStashing(
+            TouchInteractionService.TISBinder tisBinder, boolean enable) {
+        // Allow null-pointer to catch illegal states.
+        tisBinder.getTaskbarManager().getCurrentActivityContext().enableManualStashingDuringTests(
+                enable);
+    }
+
+    /**
+     * Runs the given command on the UI thread, after ensuring we are connected to
+     * TouchInteractionService.
+     */
+    protected void runOnTISBinder(Consumer<TouchInteractionService.TISBinder> connectionCallback) {
+        try {
+            CountDownLatch countDownLatch = new CountDownLatch(1);
+            TISBindHelper helper = MAIN_EXECUTOR.submit(() ->
+                    new TISBindHelper(mContext, tisBinder -> {
+                        connectionCallback.accept(tisBinder);
+                        countDownLatch.countDown();
+                    })).get();
+            countDownLatch.await();
+            MAIN_EXECUTOR.execute(helper::onDestroy);
+        } catch (ExecutionException | InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 097850f..dea4e48 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -20,6 +20,7 @@
 
 import android.annotation.TargetApi;
 import android.app.ActivityManager;
+import android.app.KeyguardManager;
 import android.os.Build;
 import android.os.Process;
 import android.os.RemoteException;
@@ -27,14 +28,13 @@
 
 import androidx.annotation.VisibleForTesting;
 
-import com.android.quickstep.util.GroupTask;
 import com.android.launcher3.util.LooperExecutor;
 import com.android.launcher3.util.SplitConfigurationOptions;
+import com.android.quickstep.util.GroupTask;
 import com.android.systemui.shared.recents.model.Task;
-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;
@@ -49,7 +49,7 @@
 
     private static final TaskLoadResult INVALID_RESULT = new TaskLoadResult(-1, false, 0);
 
-    private final KeyguardManagerCompat mKeyguardManager;
+    private final KeyguardManager mKeyguardManager;
     private final LooperExecutor mMainThreadExecutor;
     private final SystemUiProxy mSysUiProxy;
 
@@ -62,8 +62,12 @@
     private TaskLoadResult mResultsBg = INVALID_RESULT;
     private TaskLoadResult mResultsUi = INVALID_RESULT;
 
-    public RecentTasksList(LooperExecutor mainThreadExecutor,
-            KeyguardManagerCompat keyguardManager, SystemUiProxy sysUiProxy) {
+    private RecentsModel.RunningTasksListener mRunningTasksListener;
+    // Tasks are stored in order of least recently launched to most recently launched.
+    private ArrayList<ActivityManager.RunningTaskInfo> mRunningTasks;
+
+    public RecentTasksList(LooperExecutor mainThreadExecutor, KeyguardManager keyguardManager,
+            SystemUiProxy sysUiProxy) {
         mMainThreadExecutor = mainThreadExecutor;
         mKeyguardManager = keyguardManager;
         mChangeId = 1;
@@ -73,7 +77,26 @@
             public void onRecentTasksChanged() throws RemoteException {
                 mMainThreadExecutor.execute(RecentTasksList.this::onRecentTasksChanged);
             }
+
+            @Override
+            public void onRunningTaskAppeared(ActivityManager.RunningTaskInfo taskInfo) {
+                mMainThreadExecutor.execute(() -> {
+                    RecentTasksList.this.onRunningTaskAppeared(taskInfo);
+                });
+            }
+
+            @Override
+            public void onRunningTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+                mMainThreadExecutor.execute(() -> {
+                    RecentTasksList.this.onRunningTaskVanished(taskInfo);
+                });
+            }
         });
+        // We may receive onRunningTaskAppeared events later for tasks which have already been
+        // included in the list returned by mSysUiProxy.getRunningTasks(), or may receive
+        // onRunningTaskVanished for tasks not included in the returned list. These cases will be
+        // addressed when the tasks are added to/removed from mRunningTasks.
+        initRunningTasks(mSysUiProxy.getRunningTasks(Integer.MAX_VALUE));
     }
 
     @VisibleForTesting
@@ -154,6 +177,59 @@
         mChangeId++;
     }
 
+     /**
+     * Registers a listener for running tasks
+     */
+    public void registerRunningTasksListener(RecentsModel.RunningTasksListener listener) {
+        mRunningTasksListener = listener;
+    }
+
+    /**
+     * Removes the previously registered running tasks listener
+     */
+    public void unregisterRunningTasksListener() {
+        mRunningTasksListener = null;
+    }
+
+    private void initRunningTasks(ArrayList<ActivityManager.RunningTaskInfo> runningTasks) {
+        // Tasks are retrieved in order of most recently launched/used to least recently launched.
+        mRunningTasks = new ArrayList<>(runningTasks);
+        Collections.reverse(mRunningTasks);
+    }
+
+    /**
+     * Gets the set of running tasks.
+     */
+    public ArrayList<ActivityManager.RunningTaskInfo> getRunningTasks() {
+        return mRunningTasks;
+    }
+
+    private void onRunningTaskAppeared(ActivityManager.RunningTaskInfo taskInfo) {
+        // Make sure this task is not already in the list
+        for (ActivityManager.RunningTaskInfo existingTask : mRunningTasks) {
+            if (taskInfo.taskId == existingTask.taskId) {
+                return;
+            }
+        }
+        mRunningTasks.add(taskInfo);
+        if (mRunningTasksListener != null) {
+            mRunningTasksListener.onRunningTasksChanged();
+        }
+    }
+
+    private void onRunningTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+        // Find the task from the list of running tasks, if it exists
+        for (ActivityManager.RunningTaskInfo existingTask : mRunningTasks) {
+            if (existingTask.taskId != taskInfo.taskId) continue;
+
+            mRunningTasks.remove(existingTask);
+            if (mRunningTasksListener != null) {
+                mRunningTasksListener.onRunningTasksChanged();
+            }
+            return;
+        }
+    }
+
     /**
      * Loads and creates a list of all the recent tasks.
      */
@@ -178,8 +254,12 @@
 
         TaskLoadResult allTasks = new TaskLoadResult(requestId, loadKeysOnly, rawTasks.size());
         for (GroupedRecentTaskInfo rawTask : rawTasks) {
-            ActivityManager.RecentTaskInfo taskInfo1 = rawTask.mTaskInfo1;
-            ActivityManager.RecentTaskInfo taskInfo2 = rawTask.mTaskInfo2;
+            if (rawTask.getType() == GroupedRecentTaskInfo.TYPE_FREEFORM) {
+                // TODO: add entry for freeform tasks
+                continue;
+            }
+            ActivityManager.RecentTaskInfo taskInfo1 = rawTask.getTaskInfo1();
+            ActivityManager.RecentTaskInfo taskInfo2 = rawTask.getTaskInfo2();
             Task.TaskKey task1Key = new Task.TaskKey(taskInfo1);
             Task task1 = loadKeysOnly
                     ? new Task(task1Key)
@@ -195,19 +275,19 @@
                                 tmpLockedUsers.get(task2Key.userId) /* isLocked */);
                 task2.setLastSnapshotData(taskInfo2);
             }
-            final SplitConfigurationOptions.StagedSplitBounds launcherSplitBounds =
-                    convertSplitBounds(rawTask.mStagedSplitBounds);
+            final SplitConfigurationOptions.SplitBounds launcherSplitBounds =
+                    convertSplitBounds(rawTask.getSplitBounds());
             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);
     }
@@ -234,8 +314,8 @@
                 mSysUiProxy.getRecentTasks(Integer.MAX_VALUE, currentUserId);
         writer.println(prefix + "  rawTasks=[");
         for (GroupedRecentTaskInfo task : rawTasks) {
-            writer.println(prefix + "    t1=" + task.mTaskInfo1.taskId
-                    + " t2=" + (task.mTaskInfo2 != null ? task.mTaskInfo2.taskId : "-1"));
+            writer.println(prefix + "    t1=" + task.getTaskInfo1().taskId
+                    + " t2=" + (task.getTaskInfo2() != null ? task.getTaskInfo2().taskId : "-1"));
         }
         writer.println(prefix + "  ]");
     }
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 4f0b976..c879494 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -15,17 +15,13 @@
  */
 package com.android.quickstep;
 
-import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
-import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
-
 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.launcher3.testing.shared.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;
@@ -34,12 +30,12 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
+import android.app.ActivityOptions;
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
-import android.util.Log;
 import android.view.Display;
 import android.view.SurfaceControl.Transaction;
 import android.view.View;
@@ -82,7 +78,6 @@
 import com.android.quickstep.views.OverviewActionsView;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
-import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
@@ -112,8 +107,6 @@
     private @Nullable TaskbarManager mTaskbarManager;
     private @Nullable FallbackTaskbarUIController mTaskbarUIController;
 
-    private Configuration mOldConfig;
-
     private StateManager<RecentsState> mStateManager;
 
     // Strong refs to runners which are cleared when the activity is destroyed
@@ -138,7 +131,7 @@
 
         SplitSelectStateController controller =
                 new SplitSelectStateController(this, mHandler, getStateManager(),
-                        null /* depthController */);
+                        /* depthController */ null, getStatsLogManager());
         mDragLayer.recreateControllers();
         mFallbackRecentsView.init(mActionsView, controller);
 
@@ -165,7 +158,7 @@
 
     @Override
     public void onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration newConfig) {
-        onHandleConfigChanged();
+        onHandleConfigurationChanged();
         super.onMultiWindowModeChanged(isInMultiWindowMode, newConfig);
     }
 
@@ -175,11 +168,8 @@
         ACTIVITY_TRACKER.handleNewIntent(this);
     }
 
-    /**
-         * Logic for when device configuration changes (rotation, screen size change, multi-window,
-         * etc.)
-         */
-    protected void onHandleConfigChanged() {
+    @Override
+    protected void onHandleConfigurationChanged() {
         initDeviceProfile();
 
         AbstractFloatingView.closeOpenViews(this, true,
@@ -273,8 +263,10 @@
                 wrapper, RECENTS_LAUNCH_DURATION,
                 RECENTS_LAUNCH_DURATION - STATUS_BAR_TRANSITION_DURATION
                         - STATUS_BAR_TRANSITION_PRE_DELAY, getIApplicationThread());
-        final ActivityOptionsWrapper activityOptions = new ActivityOptionsWrapper(
-                ActivityOptionsCompat.makeRemoteAnimation(adapterCompat),
+        final ActivityOptions options = ActivityOptions.makeRemoteAnimation(
+                adapterCompat.getWrapped(),
+                adapterCompat.getRemoteTransition().getTransition());
+        final ActivityOptionsWrapper activityOptions = new ActivityOptionsWrapper(options,
                 onEndCallback);
         activityOptions.options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON);
         activityOptions.options.setLaunchDisplayId(
@@ -314,7 +306,6 @@
     protected void onStart() {
         // Set the alpha to 1 before calling super, as it may get set back to 0 due to
         // onActivityStart callback.
-        Log.d(BAD_STATE, "RecentsActivity onStart mFallbackRecentsView.setContentAlpha(1)");
         mFallbackRecentsView.setContentAlpha(1);
         super.onStart();
         mFallbackRecentsView.updateLocusId();
@@ -341,7 +332,6 @@
 
         mStateManager = new StateManager<>(this, RecentsState.BG_LAUNCHER);
 
-        mOldConfig = new Configuration(getResources().getConfiguration());
         initDeviceProfile();
         setupViews();
 
@@ -351,16 +341,6 @@
     }
 
     @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        int diff = newConfig.diff(mOldConfig);
-        if ((diff & (CONFIG_ORIENTATION | CONFIG_SCREEN_SIZE)) != 0) {
-            onHandleConfigChanged();
-        }
-        mOldConfig.setTo(newConfig);
-        super.onConfigurationChanged(newConfig);
-    }
-
-    @Override
     public void onStateSetEnd(RecentsState state) {
         super.onStateSetEnd(state);
 
@@ -428,8 +408,10 @@
         RemoteAnimationAdapterCompat adapterCompat =
                 new RemoteAnimationAdapterCompat(runner, HOME_APPEAR_DURATION, 0,
                         getIApplicationThread());
-        startActivity(createHomeIntent(),
-                ActivityOptionsCompat.makeRemoteAnimation(adapterCompat).toBundle());
+        ActivityOptions options = ActivityOptions.makeRemoteAnimation(
+                adapterCompat.getWrapped(),
+                adapterCompat.getRemoteTransition().getTransition());
+        startHomeIntentSafely(this, options.toBundle());
     }
 
     private final RemoteAnimationFactory mAnimationToHomeFactory =
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
index 51ae56b..b233521 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -16,7 +16,8 @@
 package com.android.quickstep;
 
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.CANCEL_RECENTS_ANIMATION;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.START_RECENTS_ANIMATION;
 
 import android.graphics.Rect;
 import android.util.ArraySet;
@@ -28,6 +29,8 @@
 
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.Preconditions;
+import com.android.quickstep.util.ActiveGestureErrorDetector;
+import com.android.quickstep.util.ActiveGestureLog;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -97,18 +100,6 @@
             RemoteAnimationTargetCompat[] appTargets,
             RemoteAnimationTargetCompat[] wallpaperTargets,
             Rect homeContentInsets, Rect minimizedHomeBounds) {
-        // Convert appTargets to type RemoteAnimationTarget for all apps except Home app
-        RemoteAnimationTarget[] nonHomeApps = Arrays.stream(appTargets)
-                .filter(remoteAnimationTarget ->
-                        remoteAnimationTarget.activityType != ACTIVITY_TYPE_HOME)
-                .map(RemoteAnimationTargetCompat::unwrap)
-                .toArray(RemoteAnimationTarget[]::new);
-
-        RemoteAnimationTarget[] nonAppTargets = mSystemUiProxy.onGoingToRecentsLegacy(nonHomeApps);
-
-        RecentsAnimationTargets targets = new RecentsAnimationTargets(appTargets,
-                wallpaperTargets, RemoteAnimationTargetCompat.wrap(nonAppTargets),
-                homeContentInsets, minimizedHomeBounds);
         mController = new RecentsAnimationController(animationController,
                 mAllowMinimizeSplitScreen, this::onAnimationFinished);
 
@@ -116,7 +107,18 @@
             Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(),
                     mController::finishAnimationToApp);
         } else {
+            final RemoteAnimationTarget[] nonAppTargets = mSystemUiProxy.onGoingToRecentsLegacy(
+                    Arrays.stream(appTargets).map(RemoteAnimationTargetCompat::unwrap)
+                            .toArray(RemoteAnimationTarget[]::new));
+            final RecentsAnimationTargets targets = new RecentsAnimationTargets(appTargets,
+                    wallpaperTargets, RemoteAnimationTargetCompat.wrap(nonAppTargets),
+                    homeContentInsets, minimizedHomeBounds);
+
             Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
+                ActiveGestureLog.INSTANCE.addLog(
+                        /* event= */ "RecentsAnimationCallbacks.onAnimationStart",
+                        /* extras= */ targets.apps.length,
+                        /* gestureEvent= */ START_RECENTS_ANIMATION);
                 for (RecentsAnimationListener listener : getListeners()) {
                     listener.onRecentsAnimationStart(mController, targets);
                 }
@@ -128,6 +130,9 @@
     @Override
     public final void onAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
         Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
+            ActiveGestureLog.INSTANCE.addLog(
+                    /* event= */ "onRecentsAnimationCancelled",
+                    /* gestureEvent= */ CANCEL_RECENTS_ANIMATION);
             for (RecentsAnimationListener listener : getListeners()) {
                 listener.onRecentsAnimationCanceled(thumbnailDatas);
             }
@@ -138,6 +143,8 @@
     @Override
     public void onTasksAppeared(RemoteAnimationTargetCompat[] apps) {
         Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
+            ActiveGestureLog.INSTANCE.addLog("onTasksAppeared",
+                    ActiveGestureErrorDetector.GestureEvent.TASK_APPEARED);
             for (RecentsAnimationListener listener : getListeners()) {
                 listener.onTasksAppeared(apps);
             }
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 2007ee1..542c0d4 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -32,6 +32,8 @@
 
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.RunnableList;
+import com.android.quickstep.util.ActiveGestureErrorDetector;
+import com.android.quickstep.util.ActiveGestureLog;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
@@ -104,8 +106,6 @@
         }
         if (mSplitScreenMinimized != splitScreenMinimized) {
             mSplitScreenMinimized = splitScreenMinimized;
-            UI_HELPER_EXECUTOR.execute(() -> SystemUiProxy.INSTANCE.get(context)
-                    .setSplitScreenMinimized(splitScreenMinimized));
         }
     }
 
@@ -174,7 +174,12 @@
      */
     @UiThread
     public void cleanupScreenshot() {
-        UI_HELPER_EXECUTOR.execute(() -> mController.cleanupScreenshot());
+        UI_HELPER_EXECUTOR.execute(() -> {
+            ActiveGestureLog.INSTANCE.addLog(
+                    "cleanupScreenshot",
+                    ActiveGestureErrorDetector.GestureEvent.CLEANUP_SCREENSHOT);
+            mController.cleanupScreenshot();
+        });
     }
 
     /**
@@ -222,7 +227,6 @@
      */
     public void enableInputConsumer() {
         UI_HELPER_EXECUTOR.submit(() -> {
-            mController.hideCurrentInputMethod();
             mController.setInputConsumerEnabled(true);
         });
     }
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 4fb7e6b..e87fdad 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -23,9 +23,9 @@
 import static com.android.launcher3.util.DisplayController.CHANGE_ALL;
 import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
 import static com.android.launcher3.util.DisplayController.CHANGE_ROTATION;
-import static com.android.launcher3.util.DisplayController.NavigationMode.NO_BUTTON;
-import static com.android.launcher3.util.DisplayController.NavigationMode.THREE_BUTTONS;
-import static com.android.launcher3.util.DisplayController.NavigationMode.TWO_BUTTONS;
+import static com.android.launcher3.util.NavigationMode.NO_BUTTON;
+import static com.android.launcher3.util.NavigationMode.THREE_BUTTONS;
+import static com.android.launcher3.util.NavigationMode.TWO_BUTTONS;
 import static com.android.launcher3.util.SettingsCache.ONE_HANDED_ENABLED;
 import static com.android.launcher3.util.SettingsCache.ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
@@ -61,12 +61,13 @@
 import android.view.MotionEvent;
 
 import androidx.annotation.BinderThread;
+import androidx.annotation.NonNull;
 
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
 import com.android.launcher3.util.DisplayController.Info;
-import com.android.launcher3.util.DisplayController.NavigationMode;
+import com.android.launcher3.util.NavigationMode;
 import com.android.launcher3.util.SettingsCache;
 import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
 import com.android.quickstep.util.NavBarPosition;
@@ -124,7 +125,7 @@
     };
 
     private int mGestureBlockingTaskId = -1;
-    private Region mExclusionRegion;
+    private @NonNull Region mExclusionRegion = new Region();
     private SystemGestureExclusionListenerCompat mExclusionListener;
 
     public RecentsAnimationDeviceState(Context context) {
@@ -162,6 +163,10 @@
             @Override
             @BinderThread
             public void onExclusionChanged(Region region) {
+                if (region == null) {
+                    // Don't think this is possible but just in case, don't let it be null.
+                    region = new Region();
+                }
                 // Assignments are atomic, it should be safe on binder thread
                 mExclusionRegion = region;
             }
@@ -498,7 +503,7 @@
     public boolean isInExclusionRegion(MotionEvent event) {
         // mExclusionRegion can change on binder thread, use a local instance here.
         Region exclusionRegion = mExclusionRegion;
-        return mMode == NO_BUTTON && exclusionRegion != null
+        return mMode == NO_BUTTON
                 && exclusionRegion.contains((int) event.getX(), (int) event.getY());
     }
 
@@ -575,19 +580,23 @@
                 && ((mSystemUiStateFlags & SYSUI_STATE_IME_SHOWING) != 0);
     }
 
+    public String getSystemUiStateString() {
+        return  QuickStepContract.getSystemUiStateString(mSystemUiStateFlags);
+    }
+
     public void dump(PrintWriter pw) {
         pw.println("DeviceState:");
         pw.println("  canStartSystemGesture=" + canStartSystemGesture());
         pw.println("  systemUiFlags=" + mSystemUiStateFlags);
-        pw.println("  systemUiFlagsDesc="
-                + QuickStepContract.getSystemUiStateString(mSystemUiStateFlags));
+        pw.println("  systemUiFlagsDesc=" + getSystemUiStateString());
         pw.println("  assistantAvailable=" + mAssistantAvailable);
         pw.println("  assistantDisabled="
                 + QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags));
         pw.println("  isUserUnlocked=" + mIsUserUnlocked);
         pw.println("  isOneHandedModeEnabled=" + mIsOneHandedModeEnabled);
         pw.println("  isSwipeToNotificationEnabled=" + mIsSwipeToNotificationEnabled);
-        pw.println("  deferredGestureRegion=" + mDeferredGestureRegion);
+        pw.println("  deferredGestureRegion=" + mDeferredGestureRegion.getBounds());
+        pw.println("  exclusionRegion=" + mExclusionRegion.getBounds());
         pw.println("  pipIsActive=" + mPipIsActive);
         mRotationTouchHelper.dump(pw);
     }
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 1634c08..3053474 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -22,6 +22,7 @@
 
 import android.annotation.TargetApi;
 import android.app.ActivityManager;
+import android.app.KeyguardManager;
 import android.content.ComponentCallbacks2;
 import android.content.Context;
 import android.content.Intent;
@@ -36,10 +37,10 @@
 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;
-import com.android.systemui.shared.system.KeyguardManagerCompat;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
 
@@ -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 =
@@ -73,10 +75,12 @@
     private RecentsModel(Context context) {
         mContext = context;
         mTaskList = new RecentTasksList(MAIN_EXECUTOR,
-                new KeyguardManagerCompat(context), SystemUiProxy.INSTANCE.get(context));
+                context.getSystemService(KeyguardManager.class),
+                SystemUiProxy.INSTANCE.get(context));
 
         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 +208,13 @@
     }
 
     @Override
+    public void onTaskIconChanged(int taskId) {
+        for (TaskVisualsChangeListener listener : mThumbnailChangeListeners) {
+            listener.onTaskIconChanged(taskId);
+        }
+    }
+
+    @Override
     public void onSystemIconStateChanged(String iconState) {
         mIconCache.clearCache();
     }
@@ -228,18 +239,33 @@
     }
 
     /**
-     * Listener for receiving various task properties changes
+     * Registers a listener for running tasks
      */
-    public interface TaskVisualsChangeListener {
+    public void registerRunningTasksListener(RunningTasksListener listener) {
+        mTaskList.registerRunningTasksListener(listener);
+    }
 
-        /**
-         * Called whn the task thumbnail changes
-         */
-        Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData);
+    /**
+     * Removes the previously registered running tasks listener
+     */
+    public void unregisterRunningTasksListener() {
+        mTaskList.unregisterRunningTasksListener();
+    }
 
+    /**
+     * Gets the set of running tasks.
+     */
+    public ArrayList<ActivityManager.RunningTaskInfo> getRunningTasks() {
+        return mTaskList.getRunningTasks();
+    }
+
+    /**
+     * Listener for receiving running tasks changes
+     */
+    public interface RunningTasksListener {
         /**
-         * Called when the icon for a task changes
+         * Called when there's a change to running tasks
          */
-        void onTaskIconChanged(String pkg, UserHandle user);
+        void onRunningTasksChanged();
     }
 }
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/RotationTouchHelper.java b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
index f1e20db..f8b6966 100644
--- a/quickstep/src/com/android/quickstep/RotationTouchHelper.java
+++ b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
@@ -23,20 +23,20 @@
 import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
 import static com.android.launcher3.util.DisplayController.CHANGE_ROTATION;
 import static com.android.launcher3.util.DisplayController.CHANGE_SUPPORTED_BOUNDS;
-import static com.android.launcher3.util.DisplayController.NavigationMode.THREE_BUTTONS;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.launcher3.util.NavigationMode.THREE_BUTTONS;
 
 import android.content.Context;
 import android.content.res.Resources;
 import android.view.MotionEvent;
 import android.view.OrientationEventListener;
 
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
 import com.android.launcher3.util.DisplayController.Info;
-import com.android.launcher3.util.DisplayController.NavigationMode;
 import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.NavigationMode;
 import com.android.quickstep.util.RecentsOrientedState;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.TaskStackChangeListener;
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..dcf685a 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -17,21 +17,25 @@
 
 import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE;
 
-import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 
+import android.app.ActivityManager;
 import android.app.PendingIntent;
 import android.app.PictureInPictureParams;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
-import android.graphics.Bitmap;
+import android.content.pm.PackageManager;
+import android.content.pm.ShortcutInfo;
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.IBinder.DeathRecipient;
+import android.os.Message;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.Log;
@@ -41,8 +45,10 @@
 import android.view.SurfaceControl;
 import android.window.IOnBackInvokedCallback;
 
-import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.DisplayController.Info;
+import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
+
+import com.android.internal.logging.InstanceId;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.systemui.shared.recents.ISystemUiProxy;
@@ -52,6 +58,7 @@
 import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController;
 import com.android.systemui.shared.system.smartspace.SmartspaceState;
 import com.android.wm.shell.back.IBackAnimation;
+import com.android.wm.shell.floating.IFloatingTasks;
 import com.android.wm.shell.onehanded.IOneHanded;
 import com.android.wm.shell.pip.IPip;
 import com.android.wm.shell.pip.IPipAnimationListener;
@@ -70,16 +77,19 @@
 /**
  * Holds the reference to SystemUI.
  */
-public class SystemUiProxy implements ISystemUiProxy, DisplayController.DisplayInfoChangeListener {
+public class SystemUiProxy implements ISystemUiProxy {
     private static final String TAG = SystemUiProxy.class.getSimpleName();
 
     public static final MainThreadInitializedObject<SystemUiProxy> INSTANCE =
             new MainThreadInitializedObject<>(SystemUiProxy::new);
 
+    private static final int MSG_SET_SHELF_HEIGHT = 1;
+
     private ISystemUiProxy mSystemUiProxy;
     private IPip mPip;
     private ISysuiUnlockAnimationController mSysuiUnlockAnimationController;
     private ISplitScreen mSplitScreen;
+    private IFloatingTasks mFloatingTasks;
     private IOneHanded mOneHanded;
     private IShellTransitions mShellTransitions;
     private IStartingWindow mStartingWindow;
@@ -96,7 +106,7 @@
     private IPipAnimationListener mPipAnimationListener;
     private ISplitScreenListener mSplitScreenListener;
     private IStartingWindowListener mStartingWindowListener;
-    private ILauncherUnlockAnimationController mPendingLauncherUnlockAnimationController;
+    private ILauncherUnlockAnimationController mLauncherUnlockAnimationController;
     private IRecentTasksListener mRecentTasksListener;
     private final ArrayList<RemoteTransitionCompat> mRemoteTransitions = new ArrayList<>();
     private IOnBackInvokedCallback mBackToLauncherCallback;
@@ -104,24 +114,16 @@
     // Used to dedupe calls to SystemUI
     private int mLastShelfHeight;
     private boolean mLastShelfVisible;
-    private float mLastNavButtonAlpha;
-    private boolean mLastNavButtonAnimate;
-    private boolean mHasNavButtonAlphaBeenSet = false;
-    private Runnable mPendingSetNavButtonAlpha = null;
+
+    private final Context mContext;
+    private final Handler mAsyncHandler;
 
     // TODO(141886704): Find a way to remove this
     private int mLastSystemUiStateFlags;
 
     public SystemUiProxy(Context context) {
-        DisplayController.INSTANCE.get(context).addChangeListener(this);
-    }
-
-    @Override
-    public void onDisplayInfoChanged(Context context, Info info, int flags) {
-        if ((flags & CHANGE_NAVIGATION_MODE) != 0) {
-            // Whenever the nav mode changes, force reset the nav button alpha
-            setNavBarButtonAlpha(1f, false);
-        }
+        mContext = context;
+        mAsyncHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), this::handleMessageAsync);
     }
 
     @Override
@@ -164,7 +166,7 @@
     }
 
     public void setProxy(ISystemUiProxy proxy, IPip pip, ISplitScreen splitScreen,
-            IOneHanded oneHanded, IShellTransitions shellTransitions,
+            IFloatingTasks floatingTasks, IOneHanded oneHanded, IShellTransitions shellTransitions,
             IStartingWindow startingWindow, IRecentTasks recentTasks,
             ISysuiUnlockAnimationController sysuiUnlockAnimationController,
             IBackAnimation backAnimation) {
@@ -172,6 +174,7 @@
         mSystemUiProxy = proxy;
         mPip = pip;
         mSplitScreen = splitScreen;
+        mFloatingTasks = floatingTasks;
         mOneHanded = oneHanded;
         mShellTransitions = shellTransitions;
         mStartingWindow = startingWindow;
@@ -189,10 +192,8 @@
         if (mStartingWindowListener != null && mStartingWindow != null) {
             setStartingWindowListener(mStartingWindowListener);
         }
-        if (mPendingLauncherUnlockAnimationController != null
-                && mSysuiUnlockAnimationController != null) {
-            setLauncherUnlockAnimationController(mPendingLauncherUnlockAnimationController);
-            mPendingLauncherUnlockAnimationController = null;
+        if (mSysuiUnlockAnimationController != null && mLauncherUnlockAnimationController != null) {
+            setLauncherUnlockAnimationController(mLauncherUnlockAnimationController);
         }
         for (int i = mRemoteTransitions.size() - 1; i >= 0; --i) {
             registerRemoteTransition(mRemoteTransitions.get(i));
@@ -203,15 +204,10 @@
         if (mBackAnimation != null && mBackToLauncherCallback != null) {
             setBackToLauncherCallback(mBackToLauncherCallback);
         }
-
-        if (mPendingSetNavButtonAlpha != null) {
-            mPendingSetNavButtonAlpha.run();
-            mPendingSetNavButtonAlpha = null;
-        }
     }
 
     public void clearProxy() {
-        setProxy(null, null, null, null, null, null, null, null, null);
+        setProxy(null, null, null, null, null, null, null, null, null, null);
     }
 
     // TODO(141886704): Find a way to remove this
@@ -271,43 +267,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;
-    }
-
-    @Override
-    public void setNavBarButtonAlpha(float alpha, boolean animate) {
-        boolean changed = Float.compare(alpha, mLastNavButtonAlpha) != 0
-                || animate != mLastNavButtonAnimate
-                || !mHasNavButtonAlphaBeenSet;
-        if (changed) {
-            if (mSystemUiProxy == null) {
-                mPendingSetNavButtonAlpha = () -> setNavBarButtonAlpha(alpha, animate);
-            } else {
-                mLastNavButtonAlpha = alpha;
-                mLastNavButtonAnimate = animate;
-                mHasNavButtonAlphaBeenSet = true;
-                try {
-                    mSystemUiProxy.setNavBarButtonAlpha(alpha, animate);
-                } catch (RemoteException e) {
-                    Log.w(TAG, "Failed call setNavBarButtonAlpha", e);
-                }
-            }
-        }
-    }
-
-    @Override
     public void onStatusBarMotionEvent(MotionEvent event) {
         if (mSystemUiProxy != null) {
             try {
@@ -385,28 +344,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 {
@@ -513,12 +450,20 @@
      * Sets the shelf height.
      */
     public void setShelfHeight(boolean visible, int shelfHeight) {
+        Message.obtain(mAsyncHandler, MSG_SET_SHELF_HEIGHT,
+                visible ? 1 : 0 , shelfHeight).sendToTarget();
+    }
+
+    @WorkerThread
+    private void setShelfHeightAsync(int visibleInt, int shelfHeight) {
+        boolean visible = visibleInt != 0;
         boolean changed = visible != mLastShelfVisible || shelfHeight != mLastShelfHeight;
-        if (mPip != null && changed) {
+        IPip pip = mPip;
+        if (pip != null && changed) {
             mLastShelfVisible = visible;
             mLastShelfHeight = shelfHeight;
             try {
-                mPip.setShelfHeight(visible, shelfHeight);
+                pip.setShelfHeight(visible, shelfHeight);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call setShelfHeight visible: " + visible
                         + " height: " + shelfHeight, e);
@@ -540,12 +485,17 @@
         mPipAnimationListener = listener;
     }
 
+    /**
+     * @return Destination bounds of auto-pip animation, {@code null} if the animation is not ready.
+     */
+    @Nullable
     public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
-            PictureInPictureParams pictureInPictureParams, int launcherRotation, int shelfHeight) {
+            PictureInPictureParams pictureInPictureParams, int launcherRotation,
+            Rect hotseatKeepClearArea) {
         if (mPip != null) {
             try {
                 return mPip.startSwipePipToHome(componentName, activityInfo,
-                        pictureInPictureParams, launcherRotation, shelfHeight);
+                        pictureInPictureParams, launcherRotation, hotseatKeepClearArea);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call startSwipePipToHome", e);
             }
@@ -598,11 +548,11 @@
     /** Start multiple tasks in split-screen simultaneously. */
     public void startTasks(int mainTaskId, Bundle mainOptions, int sideTaskId, Bundle sideOptions,
             @SplitConfigurationOptions.StagePosition int sidePosition, float splitRatio,
-            RemoteTransitionCompat remoteTransition) {
+            RemoteTransitionCompat remoteTransition, InstanceId instanceId) {
         if (mSystemUiProxy != null) {
             try {
                 mSplitScreen.startTasks(mainTaskId, mainOptions, sideTaskId, sideOptions,
-                        sidePosition, splitRatio, remoteTransition.getTransition());
+                        sidePosition, splitRatio, remoteTransition.getTransition(), instanceId);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call startTask");
             }
@@ -614,11 +564,11 @@
      */
     public void startTasksWithLegacyTransition(int mainTaskId, Bundle mainOptions, int sideTaskId,
             Bundle sideOptions, @SplitConfigurationOptions.StagePosition int sidePosition,
-            float splitRatio, RemoteAnimationAdapter adapter) {
+            float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
         if (mSystemUiProxy != null) {
             try {
                 mSplitScreen.startTasksWithLegacyTransition(mainTaskId, mainOptions, sideTaskId,
-                        sideOptions, sidePosition, splitRatio, adapter);
+                        sideOptions, sidePosition, splitRatio, adapter, instanceId);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call startTasksWithLegacyTransition");
             }
@@ -628,23 +578,38 @@
     public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent,
             Intent fillInIntent, int taskId, Bundle mainOptions, Bundle sideOptions,
             @SplitConfigurationOptions.StagePosition int sidePosition, float splitRatio,
-            RemoteAnimationAdapter adapter) {
+            RemoteAnimationAdapter adapter, InstanceId instanceId) {
         if (mSystemUiProxy != null) {
             try {
                 mSplitScreen.startIntentAndTaskWithLegacyTransition(pendingIntent, fillInIntent,
-                        taskId, mainOptions, sideOptions, sidePosition, splitRatio, adapter);
+                        taskId, mainOptions, sideOptions, sidePosition, splitRatio, adapter,
+                        instanceId);
             } catch (RemoteException e) {
-                Log.w(TAG, "Failed call startTasksWithLegacyTransition");
+                Log.w(TAG, "Failed call startIntentAndTaskWithLegacyTransition");
+            }
+        }
+    }
+
+    public void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo, int taskId,
+            Bundle mainOptions, Bundle sideOptions,
+            @SplitConfigurationOptions.StagePosition int sidePosition, float splitRatio,
+            RemoteAnimationAdapter adapter, InstanceId instanceId) {
+        if (mSystemUiProxy != null) {
+            try {
+                mSplitScreen.startShortcutAndTaskWithLegacyTransition(shortcutInfo, taskId,
+                        mainOptions, sideOptions, sidePosition, splitRatio, adapter, instanceId);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call startShortcutAndTaskWithLegacyTransition");
             }
         }
     }
 
     public void startShortcut(String packageName, String shortcutId, int position,
-            Bundle options, UserHandle user) {
+            Bundle options, UserHandle user, InstanceId instanceId) {
         if (mSplitScreen != null) {
             try {
                 mSplitScreen.startShortcut(packageName, shortcutId, position, options,
-                        user);
+                        user, instanceId);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call startShortcut");
             }
@@ -652,10 +617,10 @@
     }
 
     public void startIntent(PendingIntent intent, Intent fillInIntent, int position,
-            Bundle options) {
+            Bundle options, InstanceId instanceId) {
         if (mSplitScreen != null) {
             try {
-                mSplitScreen.startIntent(intent, fillInIntent, position, options);
+                mSplitScreen.startIntent(intent, fillInIntent, position, options, instanceId);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call startIntent");
             }
@@ -701,6 +666,20 @@
     }
 
     //
+    // Floating tasks
+    //
+
+    public void showFloatingTask(Intent intent) {
+        if (mFloatingTasks != null) {
+            try {
+                mFloatingTasks.showTask(intent);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Launcher: Failed call showFloatingTask", e);
+            }
+        }
+    }
+
+    //
     // One handed
     //
 
@@ -791,11 +770,11 @@
                     controller.dispatchSmartspaceStateToSysui();
                 }
             } catch (RemoteException e) {
-                Log.w(TAG, "Failed call setStartingWindowListener", e);
+                Log.w(TAG, "Failed call setLauncherUnlockAnimationController", e);
             }
-        } else {
-            mPendingLauncherUnlockAnimationController = controller;
         }
+
+        mLauncherUnlockAnimationController = controller;
     }
 
     /**
@@ -904,4 +883,29 @@
         }
         return new ArrayList<>();
     }
+
+    /**
+     * Gets the set of running tasks.
+     */
+    public ArrayList<ActivityManager.RunningTaskInfo> getRunningTasks(int numTasks) {
+        if (mRecentTasks != null
+                && mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_PC)) {
+            try {
+                return new ArrayList<>(Arrays.asList(mRecentTasks.getRunningTasks(numTasks)));
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call getRunningTasks", e);
+            }
+        }
+        return new ArrayList<>();
+    }
+
+    private boolean handleMessageAsync(Message msg) {
+        switch (msg.what) {
+            case MSG_SET_SHELF_HEIGHT:
+                setShelfHeightAsync(msg.arg1, msg.arg2);
+                return true;
+        }
+
+        return false;
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 54f457d..b9a1b06 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -39,13 +39,12 @@
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.RemoteTransitionCompat;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
 
-import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 
 public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAnimationListener {
@@ -156,30 +155,21 @@
             public void onTasksAppeared(RemoteAnimationTargetCompat[] appearedTaskTargets) {
                 RemoteAnimationTargetCompat appearedTaskTarget = appearedTaskTargets[0];
                 BaseActivityInterface activityInterface = mLastGestureState.getActivityInterface();
-                // Convert appTargets to type RemoteAnimationTarget for all apps except Home app
-                final ArrayList<RemoteAnimationTargetCompat> tmpNonHomeApps = new ArrayList<>();
-                final ArrayList<RemoteAnimationTargetCompat> tmpHomeApps = new ArrayList<>();
+
                 for (RemoteAnimationTargetCompat compat : appearedTaskTargets) {
-                    if (compat.activityType != ACTIVITY_TYPE_HOME) {
-                        tmpNonHomeApps.add(compat);
-                    } else {
-                        tmpHomeApps.add(compat);
+                    if (compat.activityType == ACTIVITY_TYPE_HOME
+                            && activityInterface.getCreatedActivity() instanceof RecentsActivity) {
+                        // When receive opening home activity while recents is running, enter home
+                        // and dismiss recents.
+                        ((RecentsActivity) activityInterface.getCreatedActivity()).startHome();
+                        return;
                     }
                 }
-                RemoteAnimationTarget[] nonHomeApps = tmpNonHomeApps.stream()
-                        .map(RemoteAnimationTargetCompat::unwrap)
-                        .toArray(RemoteAnimationTarget[]::new);
-                RemoteAnimationTarget[] homeApps = tmpHomeApps.stream()
-                        .map(RemoteAnimationTargetCompat::unwrap)
-                        .toArray(RemoteAnimationTarget[]::new);
-                if (homeApps.length > 0
-                        && activityInterface.getCreatedActivity() instanceof RecentsActivity) {
-                    ((RecentsActivity) activityInterface.getCreatedActivity()).startHome();
-                    return;
-                }
 
-                RemoteAnimationTarget[] nonAppTargets =
-                        SystemUiProxy.INSTANCE.getNoCreate().onStartingSplitLegacy(nonHomeApps);
+                RemoteAnimationTarget[] nonAppTargets = SystemUiProxy.INSTANCE.getNoCreate()
+                        .onStartingSplitLegacy(Arrays.stream(appearedTaskTargets)
+                                .map(RemoteAnimationTargetCompat::unwrap)
+                                .toArray(RemoteAnimationTarget[]::new));
 
                 if (ENABLE_QUICKSTEP_LIVE_TILE.get() && activityInterface.isInLiveTileMode()
                         && activityInterface.getCreatedActivity() != null) {
@@ -195,7 +185,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
@@ -235,7 +228,8 @@
             RemoteTransitionCompat transition = new RemoteTransitionCompat(mCallbacks,
                     mController != null ? mController.getController() : null,
                     mCtx.getIApplicationThread());
-            final ActivityOptions options = ActivityOptionsCompat.makeRemoteTransition(transition);
+            final ActivityOptions options = ActivityOptions.makeRemoteTransition(
+                    transition.getTransition());
             // Allowing to pause Home if Home is top activity and Recents is not Home. So when user
             // start home when recents animation is playing, the home activity can be resumed again
             // to let the transition controller collect Home activity.
diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java
index 3803f03..dc60875 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);
@@ -257,7 +263,8 @@
         if (mIconFactory == null) {
             mIconFactory = new BaseIconFactory(mContext,
                     DisplayController.INSTANCE.get(mContext).getInfo().getDensityDpi(),
-                    mContext.getResources().getDimensionPixelSize(R.dimen.taskbar_icon_size));
+                    mContext.getResources().getDimensionPixelSize(
+                            R.dimen.task_icon_cache_default_icon_size));
         }
         return mIconFactory;
     }
@@ -272,4 +279,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..eae79df 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -16,11 +16,10 @@
 
 package com.android.quickstep;
 
-import static android.view.Display.DEFAULT_DISPLAY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 
 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;
@@ -29,9 +28,16 @@
 import android.graphics.Rect;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.util.Log;
 import android.view.View;
+import android.view.WindowInsets;
+import android.view.WindowManagerGlobal;
 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 +45,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;
@@ -50,26 +57,38 @@
 import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
 import com.android.systemui.shared.recents.view.RecentsTransition;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.ActivityOptionsCompat;
-import com.android.systemui.shared.system.WindowManagerWrapper;
 
 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 +96,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
@@ -86,40 +106,10 @@
         }
     };
 
-    abstract class MultiWindowFactory implements TaskShortcutFactory {
-
-        private final int mIconRes;
-        private final int mTextRes;
-        private final LauncherEvent mLauncherEvent;
-
-        MultiWindowFactory(int iconRes, int textRes, LauncherEvent launcherEvent) {
-            mIconRes = iconRes;
-            mTextRes = textRes;
-            mLauncherEvent = launcherEvent;
-        }
-
-        protected abstract boolean isAvailable(BaseDraggingActivity activity, int displayId);
-        protected abstract ActivityOptions makeLaunchOptions(Activity activity);
-        protected abstract boolean onActivityStarted(BaseDraggingActivity activity);
-
-        @Override
-        public SystemShortcut getShortcut(BaseDraggingActivity activity,
-                TaskIdAttributeContainer taskContainer) {
-            final Task task  = taskContainer.getTask();
-            if (!task.isDockable) {
-                return null;
-            }
-            if (!isAvailable(activity, task.key.displayId)) {
-                return null;
-            }
-            return new MultiWindowSystemShortcut(mIconRes, mTextRes, activity, taskContainer, this,
-                    mLauncherEvent);
-        }
-    }
-
     class SplitSelectSystemShortcut extends SystemShortcut {
         private final TaskView mTaskView;
         private final SplitPositionOption mSplitPositionOption;
+
         public SplitSelectSystemShortcut(BaseDraggingActivity target, TaskView taskView,
                 SplitPositionOption option) {
             super(option.iconResId, option.textResId, target, taskView.getItemInfo(), taskView);
@@ -133,19 +123,18 @@
         }
     }
 
-    class MultiWindowSystemShortcut extends SystemShortcut<BaseDraggingActivity> {
+    class FreeformSystemShortcut extends SystemShortcut<BaseDraggingActivity> {
+        private static final String TAG = "FreeformSystemShortcut";
 
         private Handler mHandler;
 
         private final RecentsView mRecentsView;
         private final TaskThumbnailView mThumbnailView;
         private final TaskView mTaskView;
-        private final MultiWindowFactory mFactory;
         private final LauncherEvent mLauncherEvent;
 
-        public MultiWindowSystemShortcut(int iconRes, int textRes, BaseDraggingActivity activity,
-                TaskIdAttributeContainer taskContainer, MultiWindowFactory factory,
-                LauncherEvent launcherEvent) {
+        public FreeformSystemShortcut(int iconRes, int textRes, BaseDraggingActivity activity,
+                TaskIdAttributeContainer taskContainer, LauncherEvent launcherEvent) {
             super(iconRes, textRes, activity, taskContainer.getItemInfo(),
                     taskContainer.getTaskView());
             mLauncherEvent = launcherEvent;
@@ -153,55 +142,30 @@
             mTaskView = taskContainer.getTaskView();
             mRecentsView = activity.getOverviewPanel();
             mThumbnailView = taskContainer.getThumbnailView();
-            mFactory = factory;
         }
 
         @Override
         public void onClick(View view) {
-            Task.TaskKey taskKey = mTaskView.getTask().key;
-            final int taskId = taskKey.id;
-
-            final View.OnLayoutChangeListener onLayoutChangeListener =
-                    new View.OnLayoutChangeListener() {
-                        @Override
-                        public void onLayoutChange(View v, int l, int t, int r, int b,
-                                int oldL, int oldT, int oldR, int oldB) {
-                            mTaskView.getRootView().removeOnLayoutChangeListener(this);
-                            mRecentsView.clearIgnoreResetTask(taskId);
-
-                            // Start animating in the side pages once launcher has been resized
-                            mRecentsView.dismissTask(mTaskView, false, false);
-                        }
-                    };
-
-            final DeviceProfile.OnDeviceProfileChangeListener onDeviceProfileChangeListener =
-                    new DeviceProfile.OnDeviceProfileChangeListener() {
-                        @Override
-                        public void onDeviceProfileChanged(DeviceProfile dp) {
-                            mTarget.removeOnDeviceProfileChangeListener(this);
-                            if (dp.isMultiWindowMode) {
-                                mTaskView.getRootView().addOnLayoutChangeListener(
-                                        onLayoutChangeListener);
-                            }
-                        }
-                    };
-
             dismissTaskMenuView(mTarget);
+            RecentsView rv = mTarget.getOverviewPanel();
+            rv.switchToScreenshot(() -> {
+                rv.finishRecentsAnimation(true /* toHome */, () -> {
+                    mTarget.returnToHomescreen();
+                    rv.getHandler().post(this::startActivity);
+                });
+            });
+        }
 
-            ActivityOptions options = mFactory.makeLaunchOptions(mTarget);
+        private void startActivity() {
+            final Task.TaskKey taskKey = mTaskView.getTask().key;
+            final int taskId = taskKey.id;
+            final ActivityOptions options = makeLaunchOptions(mTarget);
             if (options != null) {
                 options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON);
             }
             if (options != null
                     && ActivityManagerWrapper.getInstance().startActivityFromRecents(taskId,
                             options)) {
-                if (!mFactory.onActivityStarted(mTarget)) {
-                    return;
-                }
-                // Add a device profile change listener to kick off animating the side tasks
-                // once we enter multiwindow mode and relayout
-                mTarget.addOnDeviceProfileChangeListener(onDeviceProfileChangeListener);
-
                 final Runnable animStartedListener = () -> {
                     // Hide the task view and wait for the window to be resized
                     // TODO: Consider animating in launcher and do an in-place start activity
@@ -233,82 +197,132 @@
                                 taskId, thumbnail, taskBounds));
                     }
                 };
-                WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture(
+                overridePendingAppTransitionMultiThumbFuture(
                         future, animStartedListener, mHandler, true /* scaleUp */,
                         taskKey.displayId);
                 mTarget.getStatsLogManager().logger().withItemInfo(mTaskView.getItemInfo())
                         .log(mLauncherEvent);
             }
         }
-    }
 
-    /** @Deprecated */
-    TaskShortcutFactory SPLIT_SCREEN = new MultiWindowFactory(R.drawable.ic_split_screen,
-            R.string.recent_task_option_split_screen, LAUNCHER_SYSTEM_SHORTCUT_SPLIT_SCREEN_TAP) {
-
-        @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);
-        }
-
-        @Override
-        protected ActivityOptions makeLaunchOptions(Activity activity) {
-            final int navBarPosition = WindowManagerWrapper.getInstance().getNavBarPosition(
-                    activity.getDisplayId());
-            if (navBarPosition == WindowManagerWrapper.NAV_BAR_POS_INVALID) {
-                return null;
+        /**
+         * Overrides a pending app transition.
+         */
+        private void overridePendingAppTransitionMultiThumbFuture(
+                AppTransitionAnimationSpecsFuture animationSpecFuture, Runnable animStartedCallback,
+                Handler animStartedCallbackHandler, boolean scaleUp, int displayId) {
+            try {
+                WindowManagerGlobal.getWindowManagerService()
+                        .overridePendingAppTransitionMultiThumbFuture(
+                                animationSpecFuture.getFuture(),
+                                RecentsTransition.wrapStartedListener(animStartedCallbackHandler,
+                                        animStartedCallback), scaleUp, displayId);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed to override pending app transition (multi-thumbnail future): ",
+                        e);
             }
-            boolean dockTopOrLeft = navBarPosition != WindowManagerWrapper.NAV_BAR_POS_LEFT;
-            return ActivityOptionsCompat.makeSplitScreenOptions(dockTopOrLeft);
         }
 
-        @Override
-        protected boolean onActivityStarted(BaseDraggingActivity activity) {
-            return true;
-        }
-    };
-
-    TaskShortcutFactory FREE_FORM = new MultiWindowFactory(R.drawable.ic_split_screen,
-            R.string.recent_task_option_freeform, LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP) {
-
-        @Override
-        protected boolean isAvailable(BaseDraggingActivity activity, int displayId) {
-            return ActivityManagerWrapper.getInstance().supportsFreeformMultiWindow(activity);
-        }
-
-        @Override
-        protected ActivityOptions makeLaunchOptions(Activity activity) {
-            ActivityOptions activityOptions = ActivityOptionsCompat.makeFreeformOptions();
+        private ActivityOptions makeLaunchOptions(Activity activity) {
+            ActivityOptions activityOptions = ActivityOptions.makeBasic();
+            activityOptions.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
             // Arbitrary bounds only because freeform is in dev mode right now
-            Rect r = new Rect(50, 50, 200, 200);
+            final View decorView = activity.getWindow().getDecorView();
+            final WindowInsets insets = decorView.getRootWindowInsets();
+            final Rect r = new Rect(0, 0, decorView.getWidth() / 2, decorView.getHeight() / 2);
+            r.offsetTo(insets.getSystemWindowInsetLeft() + 50,
+                    insets.getSystemWindowInsetTop() + 50);
             activityOptions.setLaunchBounds(r);
             return activityOptions;
         }
+    }
 
+    /**
+     * 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 onActivityStarted(BaseDraggingActivity activity) {
-            activity.returnToHomescreen();
-            return true;
+        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();
+
+            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;
+            }
+
+            return orientationHandler.getSplitPositionOptions(deviceProfile)
+                    .stream()
+                    .map((Function<SplitPositionOption, SystemShortcut>) option ->
+                            new SplitSelectSystemShortcut(activity, taskView, option))
+                    .collect(Collectors.toList());
         }
     };
 
-    TaskShortcutFactory PIN = (activity, taskContainer) -> {
-        if (!SystemUiProxy.INSTANCE.get(activity).isActive()) {
-            return null;
+    TaskShortcutFactory FREE_FORM = new TaskShortcutFactory() {
+        @Override
+        public List<SystemShortcut> getShortcuts(BaseDraggingActivity activity,
+                TaskIdAttributeContainer taskContainer) {
+            final Task task  = taskContainer.getTask();
+            if (!task.isDockable) {
+                return null;
+            }
+            if (!isAvailable(activity, task.key.displayId)) {
+                return null;
+            }
+
+            return Collections.singletonList(new FreeformSystemShortcut(R.drawable.ic_split_screen,
+                    R.string.recent_task_option_freeform, activity, taskContainer,
+                    LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP));
         }
-        if (!ActivityManagerWrapper.getInstance().isScreenPinningEnabled()) {
-            return null;
+
+        private boolean isAvailable(BaseDraggingActivity activity, int displayId) {
+            return ActivityManagerWrapper.getInstance().supportsFreeformMultiWindow(activity)
+                    && !SystemProperties.getBoolean("persist.wm.debug.desktop_mode", false);
         }
-        if (ActivityManagerWrapper.getInstance().isLockToAppActive()) {
-            // We shouldn't be able to pin while an app is locked.
-            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));
         }
-        return new PinSystemShortcut(activity, taskContainer);
     };
 
     class PinSystemShortcut extends SystemShortcut<BaseDraggingActivity> {
@@ -335,26 +349,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/TaskUtils.java b/quickstep/src/com/android/quickstep/TaskUtils.java
index c9db153..d722778 100644
--- a/quickstep/src/com/android/quickstep/TaskUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskUtils.java
@@ -18,6 +18,8 @@
 
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -25,6 +27,8 @@
 import android.os.UserHandle;
 import android.util.Log;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.PackageManagerHelper;
@@ -47,16 +51,32 @@
      * TODO: remove this once we switch to getting the icon and label from IconCache.
      */
     public static CharSequence getTitle(Context context, Task task) {
-        UserHandle user = UserHandle.of(task.key.userId);
+        return getTitle(context, task.key.userId, task.getTopComponent().getPackageName());
+    }
+
+    public static CharSequence getTitle(
+            @NonNull Context context,
+            @UserIdInt @Nullable Integer userId,
+            @Nullable String packageName) {
+        if (userId == null || packageName == null) {
+            if (userId == null) {
+                Log.e(TAG, "Failed to get title; missing userId");
+            }
+            if (packageName == null) {
+                Log.e(TAG, "Failed to get title; missing packageName");
+            }
+            return "";
+        }
+        UserHandle user = UserHandle.of(userId);
         ApplicationInfo applicationInfo = new PackageManagerHelper(context)
-                .getApplicationInfo(task.getTopComponent().getPackageName(), user, 0);
+                .getApplicationInfo(packageName, user, 0);
         if (applicationInfo == null) {
-            Log.e(TAG, "Failed to get title for task " + task);
+            Log.e(TAG, "Failed to get title for userId=" + userId + ", packageName=" + packageName);
             return "";
         }
         PackageManager packageManager = context.getPackageManager();
         return packageManager.getUserBadgedLabel(
-            applicationInfo.loadLabel(packageManager), user);
+                applicationInfo.loadLabel(packageManager), user);
     }
 
     public static ComponentKey getLaunchComponentKeyForTask(Task.TaskKey taskKey) {
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index db402af..a96524e 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -36,15 +36,13 @@
 import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
 import static com.android.launcher3.anim.Interpolators.clampToProgress;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.launcher3.statehandlers.DepthController.DEPTH;
-import static com.android.launcher3.testing.TestProtocol.BAD_STATE;
+import static com.android.launcher3.statehandlers.DepthController.STATE_DEPTH;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
 import android.app.PendingIntent;
@@ -55,7 +53,6 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.os.Build;
-import android.util.Log;
 import android.view.SurfaceControl;
 import android.view.View;
 import android.window.TransitionInfo;
@@ -197,10 +194,10 @@
 
         int taskIndex = recentsView.indexOfChild(v);
         Context context = v.getContext();
-        DeviceProfile dp = BaseActivity.fromContext(context).getDeviceProfile();
+        BaseActivity baseActivity = BaseActivity.fromContext(context);
+        DeviceProfile dp = baseActivity.getDeviceProfile();
         boolean showAsGrid = dp.isTablet;
-        boolean parallaxCenterAndAdjacentTask =
-                taskIndex != recentsView.getCurrentPage() && !showAsGrid;
+        boolean parallaxCenterAndAdjacentTask = taskIndex != recentsView.getCurrentPage();
         int taskRectTranslationPrimary = recentsView.getScrollOffset(taskIndex);
         int taskRectTranslationSecondary = showAsGrid ? (int) v.getGridTranslationY() : 0;
 
@@ -370,7 +367,7 @@
         });
 
         if (depthController != null) {
-            out.setFloat(depthController, DEPTH, BACKGROUND_APP.getDepth(context),
+            out.setFloat(depthController, STATE_DEPTH, BACKGROUND_APP.getDepth(baseActivity),
                     TOUCH_RESPONSE_INTERPOLATOR);
         }
     }
@@ -391,10 +388,39 @@
      * device is considered in multiWindowMode and things like insets and stuff change
      * and calculations have to be adjusted in the animations for that
      */
-    public static void composeRecentsSplitLaunchAnimator(int initialTaskId,
-            @Nullable PendingIntent initialTaskPendingIntent, int secondTaskId,
+    public static void composeRecentsSplitLaunchAnimator(GroupedTaskView launchingTaskView,
+            @NonNull StateManager stateManager, @Nullable DepthController depthController,
+            int initialTaskId, @Nullable PendingIntent initialTaskPendingIntent, int secondTaskId,
             @NonNull TransitionInfo transitionInfo, SurfaceControl.Transaction t,
             @NonNull Runnable finishCallback) {
+        if (launchingTaskView != null) {
+            AnimatorSet animatorSet = new AnimatorSet();
+            animatorSet.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    finishCallback.run();
+                }
+            });
+
+            final RemoteAnimationTargetCompat[] appTargets =
+                    RemoteAnimationTargetCompat.wrapApps(transitionInfo, t, null /* leashMap */);
+            final RemoteAnimationTargetCompat[] wallpaperTargets =
+                    RemoteAnimationTargetCompat.wrapNonApps(
+                            transitionInfo, true /* wallpapers */, t, null /* leashMap */);
+            final RemoteAnimationTargetCompat[] nonAppTargets =
+                    RemoteAnimationTargetCompat.wrapNonApps(
+                            transitionInfo, false /* wallpapers */, t, null /* leashMap */);
+            final RecentsView recentsView = launchingTaskView.getRecentsView();
+            composeRecentsLaunchAnimator(animatorSet, launchingTaskView,
+                    appTargets, wallpaperTargets, nonAppTargets,
+                    true, stateManager,
+                    recentsView, depthController);
+
+            t.apply();
+            animatorSet.start();
+            return;
+        }
+
         // TODO: consider initialTaskPendingIntent
         TransitionInfo.Change splitRoot1 = null;
         TransitionInfo.Change splitRoot2 = null;
@@ -468,7 +494,6 @@
             animatorSet.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
-                    super.onAnimationEnd(animation);
                     finishCallback.run();
                 }
             });
@@ -530,7 +555,6 @@
                 for (SurfaceControl leash: closingTargets) {
                     t.hide(leash);
                 }
-                super.onAnimationEnd(animation);
                 finishCallback.run();
             }
         });
@@ -568,40 +592,27 @@
         Animator launcherAnim;
         final AnimatorListenerAdapter windowAnimEndListener;
         if (launcherClosing) {
-            Context context = v.getContext();
-            DeviceProfile dp = BaseActivity.fromContext(context).getDeviceProfile();
-            launcherAnim = dp.isTablet
-                    ? ObjectAnimator.ofFloat(recentsView, RecentsView.CONTENT_ALPHA, 0)
-                    : recentsView.createAdjacentPageAnimForTaskLaunch(taskView);
-            if (dp.isTablet) {
-                Log.d(BAD_STATE, "TVU composeRecentsLaunchAnimator alpha=" + 0);
-                launcherAnim.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationStart(Animator animation) {
-                        Log.d(BAD_STATE, "TVU composeRecentsLaunchAnimator onStart");
-                    }
-
-                    @Override
-                    public void onAnimationCancel(Animator animation) {
-                        float alpha = recentsView == null
-                                ? -1
-                                : RecentsView.CONTENT_ALPHA.get(recentsView);
-                        Log.d(BAD_STATE, "TVU composeRecentsLaunchAnimator onCancel, alpha="
-                                + alpha);
-                    }
-
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        Log.d(BAD_STATE, "TVU composeRecentsLaunchAnimator onEnd");
-                    }
-                });
+            // Since Overview is in launcher, just opening overview sets willFinishToHome to true.
+            // Now that we are closing the launcher, we need to (re)set willFinishToHome back to
+            // false. Otherwise, RecentsAnimationController can't differentiate between closing
+            // overview to 3p home vs closing overview to app.
+            final RecentsAnimationController raController =
+                    recentsView.getRecentsAnimationController();
+            if (raController != null) {
+                raController.setWillFinishToHome(false);
             }
+            launcherAnim = recentsView.createAdjacentPageAnimForTaskLaunch(taskView);
             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(() -> {
@@ -657,7 +668,7 @@
         for (int i = 0; i < nonApps.length; ++i) {
             final RemoteAnimationTargetCompat targ = nonApps[i];
             final SurfaceControl leash = targ.leash;
-            if (targ.windowType == TYPE_DOCK_DIVIDER && leash != null) {
+            if (targ.windowType == TYPE_DOCK_DIVIDER && leash != null && leash.isValid()) {
                 auxiliarySurfaces.add(leash);
                 hasSurfaceToAnimate = true;
             }
@@ -670,16 +681,18 @@
         dockFadeAnimator.addUpdateListener(valueAnimator -> {
             float progress = valueAnimator.getAnimatedFraction();
             for (SurfaceControl leash : auxiliarySurfaces) {
-                t.setAlpha(leash, shown ? progress : 1 - progress);
+                if (leash != null && leash.isValid()) {
+                    t.setAlpha(leash, shown ? progress : 1 - progress);
+                }
             }
             t.apply();
         });
         dockFadeAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
-                super.onAnimationStart(animation);
                 if (shown) {
                     for (SurfaceControl leash : auxiliarySurfaces) {
+                        t.setLayer(leash, Integer.MAX_VALUE);
                         t.setAlpha(leash, 0);
                         t.show(leash);
                     }
@@ -689,10 +702,11 @@
 
             @Override
             public void onAnimationEnd(Animator animation) {
-                super.onAnimationEnd(animation);
                 if (!shown) {
                     for (SurfaceControl leash : auxiliarySurfaces) {
-                        t.hide(leash);
+                        if (leash != null && leash.isValid()) {
+                            t.hide(leash);
+                        }
                     }
                     t.apply();
                 }
diff --git a/quickstep/src/com/android/quickstep/TopTaskTracker.java b/quickstep/src/com/android/quickstep/TopTaskTracker.java
index 723dc72..d4bf5c7 100644
--- a/quickstep/src/com/android/quickstep/TopTaskTracker.java
+++ b/quickstep/src/com/android/quickstep/TopTaskTracker.java
@@ -20,9 +20,11 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.content.Intent.ACTION_CHOOSER;
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
+import static android.view.Display.DEFAULT_DISPLAY;
 
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
 
+import android.annotation.UserIdInt;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
 
@@ -31,9 +33,9 @@
 
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.SplitConfigurationOptions;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitStageInfo;
 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.TraceHelper;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.Task.TaskKey;
@@ -63,8 +65,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) {
@@ -84,6 +87,19 @@
     public void onTaskMovedToFront(RunningTaskInfo taskInfo) {
         mOrderedTaskList.removeIf(rto -> rto.taskId == taskInfo.taskId);
         mOrderedTaskList.addFirst(taskInfo);
+
+        // Keep the home display's top running task in the first while adding a non-home
+        // display's task to the list, to avoid showing non-home display's task upon going to
+        // Recents animation.
+        if (taskInfo.displayId != DEFAULT_DISPLAY) {
+            final RunningTaskInfo topTaskOnHomeDisplay = mOrderedTaskList.stream()
+                    .filter(rto -> rto.displayId == DEFAULT_DISPLAY).findFirst().orElse(null);
+            if (topTaskOnHomeDisplay != null) {
+                mOrderedTaskList.removeIf(rto -> rto.taskId == topTaskOnHomeDisplay.taskId);
+                mOrderedTaskList.addFirst(topTaskOnHomeDisplay);
+            }
+        }
+
         if (mOrderedTaskList.size() >= HISTORY_SIZE) {
             // If we grow in size, remove the last taskInfo which is not part of the split task.
             Iterator<RunningTaskInfo> itr = mOrderedTaskList.descendingIterator();
@@ -144,8 +160,8 @@
         mPinnedTaskId = INVALID_TASK_ID;
     }
 
-    private void resetTaskId(StagedSplitTaskPosition taskPosition) {
-        taskPosition.taskId = INVALID_TASK_ID;
+    private void resetTaskId(SplitStageInfo taskPosition) {
+        taskPosition.taskId = -1;
     }
 
     /**
@@ -267,5 +283,16 @@
             }
             return result;
         }
+
+        @UserIdInt
+        @Nullable
+        public Integer getUserId() {
+            return mTopTask == null ? null : mTopTask.userId;
+        }
+
+        @Nullable
+        public String getPackageName() {
+            return mTopTask == null ? null : mTopTask.baseActivity.getPackageName();
+        }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 61d36fb..e207a1b 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -23,9 +23,12 @@
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.quickstep.GestureState.DEFAULT_STATE;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_DOWN;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_UP;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_RECENT_TASKS;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_BACK_ANIMATION;
+import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_FLOATING_TASKS;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_ONE_HANDED;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_PIP;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
@@ -51,6 +54,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;
@@ -64,7 +68,6 @@
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.R;
-import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.provider.RestoreDbTask;
@@ -72,7 +75,8 @@
 import com.android.launcher3.taskbar.TaskbarActivityContext;
 import com.android.launcher3.taskbar.TaskbarManager;
 import com.android.launcher3.testing.TestLogging;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.ResourceUtils;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.tracing.LauncherTraceProto;
 import com.android.launcher3.tracing.TouchInteractionServiceProto;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
@@ -93,9 +97,11 @@
 import com.android.quickstep.inputconsumers.SysUiOverlayInputConsumer;
 import com.android.quickstep.inputconsumers.TaskbarStashInputConsumer;
 import com.android.quickstep.util.ActiveGestureLog;
+import com.android.quickstep.util.ActiveGestureLog.CompoundString;
 import com.android.quickstep.util.ProtoTracer;
 import com.android.quickstep.util.ProxyScreenStatusProvider;
 import com.android.quickstep.util.SplitScreenBounds;
+import com.android.quickstep.util.ViewCapture;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -105,6 +111,7 @@
 import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController;
 import com.android.systemui.shared.tracing.ProtoTraceable;
 import com.android.wm.shell.back.IBackAnimation;
+import com.android.wm.shell.floating.IFloatingTasks;
 import com.android.wm.shell.onehanded.IOneHanded;
 import com.android.wm.shell.pip.IPip;
 import com.android.wm.shell.recents.IRecentTasks;
@@ -125,8 +132,14 @@
 public class TouchInteractionService extends Service
         implements ProtoTraceable<LauncherTraceProto.Builder> {
 
+    private static final String SUBSTRING_PREFIX = "; ";
+    private static final String NEWLINE_PREFIX = "\n\t\t\t-> ";
+
     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";
@@ -156,6 +169,8 @@
             IPip pip = IPip.Stub.asInterface(bundle.getBinder(KEY_EXTRA_SHELL_PIP));
             ISplitScreen splitscreen = ISplitScreen.Stub.asInterface(bundle.getBinder(
                     KEY_EXTRA_SHELL_SPLIT_SCREEN));
+            IFloatingTasks floatingTasks = IFloatingTasks.Stub.asInterface(bundle.getBinder(
+                    KEY_EXTRA_SHELL_FLOATING_TASKS));
             IOneHanded onehanded = IOneHanded.Stub.asInterface(
                     bundle.getBinder(KEY_EXTRA_SHELL_ONE_HANDED));
             IShellTransitions shellTransitions = IShellTransitions.Stub.asInterface(
@@ -171,8 +186,8 @@
                     bundle.getBinder(KEY_EXTRA_SHELL_BACK_ANIMATION));
             MAIN_EXECUTOR.execute(() -> {
                 SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(proxy, pip,
-                        splitscreen, onehanded, shellTransitions, startingWindow, recentTasks,
-                        launcherUnlockAnimationController, backAnimation);
+                        splitscreen, floatingTasks, onehanded, shellTransitions, startingWindow,
+                        recentTasks, launcherUnlockAnimationController, backAnimation);
                 TouchInteractionService.this.initInputMonitor("TISBinder#onInitialize()");
                 preloadOverview(true /* fromInit */);
             });
@@ -266,6 +281,18 @@
             MAIN_EXECUTOR.execute(ProxyScreenStatusProvider.INSTANCE::onScreenTurnedOn);
         }
 
+        @BinderThread
+        @Override
+        public void onScreenTurningOn() {
+            MAIN_EXECUTOR.execute(ProxyScreenStatusProvider.INSTANCE::onScreenTurningOn);
+        }
+
+        @BinderThread
+        @Override
+        public void onScreenTurningOff() {
+            MAIN_EXECUTOR.execute(ProxyScreenStatusProvider.INSTANCE::onScreenTurningOff);
+        }
+
         /**
          * Preloads the Overview activity.
          *
@@ -439,7 +466,8 @@
         mOverviewComponentObserver = new OverviewComponentObserver(this, mDeviceState);
         mOverviewCommandHelper = new OverviewCommandHelper(this,
                 mOverviewComponentObserver, mTaskAnimationManager);
-        mResetGestureInputConsumer = new ResetGestureInputConsumer(mTaskAnimationManager);
+        mResetGestureInputConsumer = new ResetGestureInputConsumer(
+                mTaskAnimationManager, mTaskbarManager::getCurrentActivityContext);
         mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer();
         mInputConsumer.registerInputConsumer();
         onSystemUiFlagsChanged(mDeviceState.getSystemUiStateFlags());
@@ -603,8 +631,6 @@
                 mConsumer.onConsumerAboutToBeSwitched();
                 mGestureState = newGestureState;
                 mConsumer = newConsumer(prevGestureState, mGestureState, event);
-
-                ActiveGestureLog.INSTANCE.addLog("setInputConsumer: " + mConsumer.getName());
                 mUncheckedConsumer = mConsumer;
             } else if (mDeviceState.isUserUnlocked() && mDeviceState.isFullyGesturalNavMode()
                     && mDeviceState.canTriggerAssistantAction(event)) {
@@ -612,8 +638,7 @@
                 // Do not change mConsumer as if there is an ongoing QuickSwitch gesture, we
                 // should not interrupt it. QuickSwitch assumes that interruption can only
                 // happen if the next gesture is also quick switch.
-                mUncheckedConsumer = tryCreateAssistantInputConsumer(
-                        InputConsumer.NO_OP, mGestureState, event);
+                mUncheckedConsumer = tryCreateAssistantInputConsumer(mGestureState, event);
             } else if (mDeviceState.canTriggerOneHandedAction(event)) {
                 // Consume gesture event for triggering one handed feature.
                 mUncheckedConsumer = new OneHandedModeInputConsumer(this, mDeviceState,
@@ -633,12 +658,17 @@
             switch (event.getActionMasked()) {
                 case ACTION_DOWN:
                 case ACTION_UP:
-                    ActiveGestureLog.INSTANCE.addLog("onMotionEvent("
-                            + (int) event.getRawX() + ", " + (int) event.getRawY() + ")",
-                            event.getActionMasked());
+                    ActiveGestureLog.INSTANCE.addLog(
+                            /* event= */ "onMotionEvent(" + (int) event.getRawX() + ", "
+                                    + (int) event.getRawY() + "): "
+                                    + MotionEvent.actionToString(event.getActionMasked()),
+                            /* gestureEvent= */ event.getActionMasked() == ACTION_DOWN
+                                    ? MOTION_DOWN
+                                    : MOTION_UP);
                     break;
                 default:
-                    ActiveGestureLog.INSTANCE.addLog("onMotionEvent", event.getActionMasked());
+                    ActiveGestureLog.INSTANCE.addLog("onMotionEvent: "
+                            + MotionEvent.actionToString(event.getActionMasked()));
                     break;
             }
         }
@@ -660,118 +690,247 @@
         ProtoTracer.INSTANCE.get(this).scheduleFrameUpdate();
     }
 
-    private InputConsumer tryCreateAssistantInputConsumer(InputConsumer base,
+    private InputConsumer tryCreateAssistantInputConsumer(
             GestureState gestureState, MotionEvent motionEvent) {
-        return mDeviceState.isGestureBlockedTask(gestureState.getRunningTask())
-                ? base
-                : new AssistantInputConsumer(this, gestureState, base, mInputMonitorCompat,
-                        mDeviceState, motionEvent);
+        return tryCreateAssistantInputConsumer(
+                InputConsumer.NO_OP, gestureState, motionEvent, CompoundString.NO_OP);
+    }
+
+    private InputConsumer tryCreateAssistantInputConsumer(
+            InputConsumer base,
+            GestureState gestureState,
+            MotionEvent motionEvent,
+            CompoundString reasonString) {
+        if (mDeviceState.isGestureBlockedTask(gestureState.getRunningTask())) {
+            reasonString.append(SUBSTRING_PREFIX)
+                    .append("is gesture-blocked task, using base input consumer");
+            return base;
+        } else {
+            reasonString.append(SUBSTRING_PREFIX).append("using AssistantInputConsumer");
+            return new AssistantInputConsumer(
+                    this, gestureState, base, mInputMonitorCompat, mDeviceState, motionEvent);
+        }
     }
 
     public GestureState createGestureState(GestureState previousGestureState) {
-        GestureState gestureState = new GestureState(mOverviewComponentObserver,
-                ActiveGestureLog.INSTANCE.generateAndSetLogId());
+        final GestureState gestureState;
+        TopTaskTracker.CachedTaskInfo taskInfo;
         if (mTaskAnimationManager.isRecentsAnimationRunning()) {
-            gestureState.updateRunningTask(previousGestureState.getRunningTask());
+            gestureState = new GestureState(mOverviewComponentObserver,
+                    ActiveGestureLog.INSTANCE.getLogId());
+            taskInfo = previousGestureState.getRunningTask();
+            gestureState.updateRunningTask(taskInfo);
             gestureState.updateLastStartedTaskId(previousGestureState.getLastStartedTaskId());
             gestureState.updatePreviouslyAppearedTaskIds(
                     previousGestureState.getPreviouslyAppearedTaskIds());
         } else {
-            gestureState.updateRunningTask(
-                    TopTaskTracker.INSTANCE.get(this).getCachedTopTask(false));
+            gestureState = new GestureState(mOverviewComponentObserver,
+                    ActiveGestureLog.INSTANCE.incrementLogId());
+            taskInfo = TopTaskTracker.INSTANCE.get(this).getCachedTopTask(false);
+            gestureState.updateRunningTask(taskInfo);
         }
+        // Log initial state for the gesture.
+        ActiveGestureLog.INSTANCE.addLog(new CompoundString("Current running task package name=")
+                .append(taskInfo == null ? "no running task" : taskInfo.getPackageName()));
+        ActiveGestureLog.INSTANCE.addLog(new CompoundString("Current SystemUi state flags=")
+                .append(mDeviceState.getSystemUiStateString()));
         return gestureState;
     }
 
-    private InputConsumer newConsumer(GestureState previousGestureState,
-            GestureState newGestureState, MotionEvent event) {
+    private InputConsumer newConsumer(
+            GestureState previousGestureState, GestureState newGestureState, MotionEvent event) {
         AnimatedFloat progressProxy = mSwipeUpProxyProvider.apply(mGestureState);
         if (progressProxy != null) {
-            return new ProgressDelegateInputConsumer(this, mTaskAnimationManager,
-                    mGestureState, mInputMonitorCompat, progressProxy);
+            InputConsumer consumer = new ProgressDelegateInputConsumer(
+                    this, mTaskAnimationManager, mGestureState, mInputMonitorCompat, progressProxy);
+
+            logInputConsumerSelectionReason(consumer, newCompoundString(
+                    "mSwipeUpProxyProvider has been set, using ProgressDelegateInputConsumer"));
+
+            return consumer;
         }
 
         boolean canStartSystemGesture = mDeviceState.canStartSystemGesture();
 
         if (!mDeviceState.isUserUnlocked()) {
+            CompoundString reasonString = newCompoundString("device locked");
+            InputConsumer consumer;
             if (canStartSystemGesture) {
                 // This handles apps launched in direct boot mode (e.g. dialer) as well as apps
                 // launched while device is locked even after exiting direct boot mode (e.g. camera).
-                return createDeviceLockedInputConsumer(newGestureState);
+                consumer = createDeviceLockedInputConsumer(
+                        newGestureState, reasonString.append(SUBSTRING_PREFIX)
+                                .append("can start system gesture"));
             } else {
-                return getDefaultInputConsumer();
+                consumer = getDefaultInputConsumer(
+                        reasonString.append(SUBSTRING_PREFIX)
+                                .append("cannot start system gesture"));
             }
+            logInputConsumerSelectionReason(consumer, reasonString);
+            return consumer;
         }
 
+        CompoundString reasonString;
+        InputConsumer base;
         // When there is an existing recents animation running, bypass systemState check as this is
         // a followup gesture and the first gesture started in a valid system state.
-        InputConsumer base = canStartSystemGesture
-                || previousGestureState.isRecentsAnimationRunning()
-                        ? newBaseConsumer(previousGestureState, newGestureState, event)
-                        : getDefaultInputConsumer();
+        if (canStartSystemGesture || previousGestureState.isRecentsAnimationRunning()) {
+            reasonString = newCompoundString(canStartSystemGesture
+                    ? "can start system gesture" : "recents animation was running")
+                    .append(", trying to use base consumer");
+            base = newBaseConsumer(previousGestureState, newGestureState, event, reasonString);
+        } else {
+            reasonString = newCompoundString(
+                    "cannot start system gesture and recents animation was not running")
+                    .append(", trying to use default input consumer");
+            base = getDefaultInputConsumer(reasonString);
+        }
         if (mDeviceState.isGesturalNavMode()) {
             handleOrientationSetup(base);
         }
         if (mDeviceState.isFullyGesturalNavMode()) {
+            String reasonPrefix = "device is in gesture navigation mode";
             if (mDeviceState.canTriggerAssistantAction(event)) {
-                base = tryCreateAssistantInputConsumer(base, newGestureState, event);
+                reasonString.append(NEWLINE_PREFIX)
+                        .append(reasonPrefix)
+                        .append(SUBSTRING_PREFIX)
+                        .append("gesture can trigger the assistant")
+                        .append(", trying to use assistant input consumer");
+                base = tryCreateAssistantInputConsumer(base, newGestureState, event, reasonString);
             }
 
             // If Taskbar is present, we listen for long press to unstash it.
             TaskbarActivityContext tac = mTaskbarManager.getCurrentActivityContext();
             if (tac != null) {
+                reasonString.append(NEWLINE_PREFIX)
+                        .append(reasonPrefix)
+                        .append(SUBSTRING_PREFIX)
+                        .append("TaskbarActivityContext != null, using TaskbarStashInputConsumer");
                 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()) {
+                reasonString = newCompoundString(reasonPrefix)
+                        .append(SUBSTRING_PREFIX)
+                        .append("bubbles expanded");
+                if (BUBBLES_HOME_GESTURE_ENABLED) {
+                    reasonString.append(SUBSTRING_PREFIX)
+                            .append("bubbles can handle the home gesture")
+                            .append(", trying to use default input consumer");
+                    // Bubbles can handle home gesture itself.
+                    base = getDefaultInputConsumer(reasonString);
+                } 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()) {
+                        reasonString = newCompoundString(reasonPrefix)
+                                .append(SUBSTRING_PREFIX)
+                                .append("using SysUiOverlayInputConsumer");
+                        base = new SysUiOverlayInputConsumer(
+                                getBaseContext(), mDeviceState, mInputMonitorCompat);
+                    }
+                }
+            }
+
+            if (mDeviceState.isSystemUiDialogShowing()) {
+                reasonString = newCompoundString(reasonPrefix)
+                        .append(SUBSTRING_PREFIX)
+                        .append("system dialog is showing, using SysUiOverlayInputConsumer");
                 base = new SysUiOverlayInputConsumer(
                         getBaseContext(), mDeviceState, mInputMonitorCompat);
             }
 
+
+
             if (mDeviceState.isScreenPinningActive()) {
+                reasonString = newCompoundString(reasonPrefix)
+                        .append(SUBSTRING_PREFIX)
+                        .append("screen pinning is active, using ScreenPinnedInputConsumer");
                 // 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).
                 base = new ScreenPinnedInputConsumer(this, newGestureState);
             }
 
             if (mDeviceState.canTriggerOneHandedAction(event)) {
-                base = new OneHandedModeInputConsumer(this, mDeviceState, base,
-                        mInputMonitorCompat);
+                reasonString.append(NEWLINE_PREFIX)
+                        .append(reasonPrefix)
+                        .append(SUBSTRING_PREFIX)
+                        .append("gesture can trigger one handed mode")
+                        .append(", using OneHandedModeInputConsumer");
+                base = new OneHandedModeInputConsumer(
+                        this, mDeviceState, base, mInputMonitorCompat);
             }
 
             if (mDeviceState.isAccessibilityMenuAvailable()) {
-                base = new AccessibilityInputConsumer(this, mDeviceState, base,
-                        mInputMonitorCompat);
+                reasonString.append(NEWLINE_PREFIX)
+                        .append(reasonPrefix)
+                        .append(SUBSTRING_PREFIX)
+                        .append("accessibility menu is available")
+                        .append(", using AccessibilityInputConsumer");
+                base = new AccessibilityInputConsumer(
+                        this, mDeviceState, base, mInputMonitorCompat);
             }
         } else {
+            String reasonPrefix = "device is not in gesture navigation mode";
             if (mDeviceState.isScreenPinningActive()) {
-                base = getDefaultInputConsumer();
+                reasonString = newCompoundString(reasonPrefix)
+                        .append(SUBSTRING_PREFIX)
+                        .append("screen pinning is active, trying to use default input consumer");
+                base = getDefaultInputConsumer(reasonString);
             }
 
             if (mDeviceState.canTriggerOneHandedAction(event)) {
-                base = new OneHandedModeInputConsumer(this, mDeviceState, base,
-                        mInputMonitorCompat);
+                reasonString.append(NEWLINE_PREFIX)
+                        .append(reasonPrefix)
+                        .append(SUBSTRING_PREFIX)
+                        .append("gesture can trigger one handed mode")
+                        .append(", using OneHandedModeInputConsumer");
+                base = new OneHandedModeInputConsumer(
+                        this, mDeviceState, base, mInputMonitorCompat);
             }
         }
+        logInputConsumerSelectionReason(base, reasonString);
         return base;
     }
 
+    private CompoundString newCompoundString(String substring) {
+        return new CompoundString(NEWLINE_PREFIX).append(substring);
+    }
+
+    private void logInputConsumerSelectionReason(
+            InputConsumer consumer, CompoundString reasonString) {
+        if (!FeatureFlags.ENABLE_INPUT_CONSUMER_REASON_LOGGING.get()) {
+            ActiveGestureLog.INSTANCE.addLog("setInputConsumer: " + consumer.getName());
+            return;
+        }
+        ActiveGestureLog.INSTANCE.addLog(new CompoundString("setInputConsumer: ")
+                .append(consumer.getName())
+                .append(". reason(s):")
+                .append(reasonString));
+    }
+
     private void handleOrientationSetup(InputConsumer baseInputConsumer) {
         baseInputConsumer.notifyOrientationSetup();
     }
 
-    private InputConsumer newBaseConsumer(GestureState previousGestureState,
-            GestureState gestureState, MotionEvent event) {
+    private InputConsumer newBaseConsumer(
+            GestureState previousGestureState,
+            GestureState gestureState,
+            MotionEvent event,
+            CompoundString reasonString) {
         if (mDeviceState.isKeyguardShowingOccluded()) {
             // This handles apps showing over the lockscreen (e.g. camera)
-            return createDeviceLockedInputConsumer(gestureState);
+            return createDeviceLockedInputConsumer(
+                    gestureState,
+                    reasonString.append(SUBSTRING_PREFIX)
+                            .append("keyguard is showing occluded")
+                            .append(", trying to use device locked input consumer"));
         }
 
+        reasonString.append(SUBSTRING_PREFIX).append("keyguard is not showing occluded");
         // Use overview input consumer for sharesheets on top of home.
         boolean forceOverviewInputConsumer = gestureState.getActivityInterface().isStarted()
                 && gestureState.getRunningTask() != null
@@ -785,23 +944,46 @@
             forceOverviewInputConsumer = gestureState.getRunningTask().isHomeTask();
         }
 
+        boolean previousGestureAnimatedToLauncher =
+                previousGestureState.isRunningAnimationToLauncher();
+        // with shell-transitions, home is resumed during recents animation, so
+        // explicitly check against recents animation too.
+        boolean launcherResumedThroughShellTransition =
+                gestureState.getActivityInterface().isResumed()
+                        && !previousGestureState.isRecentsAnimationRunning();
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()
                 && gestureState.getActivityInterface().isInLiveTileMode()) {
             return createOverviewInputConsumer(
-                    previousGestureState, gestureState, event, forceOverviewInputConsumer);
+                    previousGestureState,
+                    gestureState,
+                    event,
+                    forceOverviewInputConsumer,
+                    reasonString.append(SUBSTRING_PREFIX)
+                            .append("is in live tile mode, trying to use overview input consumer"));
         } else if (gestureState.getRunningTask() == null) {
-            return getDefaultInputConsumer();
-        } else if (previousGestureState.isRunningAnimationToLauncher()
-                || (gestureState.getActivityInterface().isResumed()
-                        // with shell-transitions, home is resumed during recents animation, so
-                        // explicitly check against recents animation too.
-                        && !previousGestureState.isRecentsAnimationRunning())
+            return getDefaultInputConsumer(reasonString.append(SUBSTRING_PREFIX)
+                    .append("running task == null"));
+        } else if (previousGestureAnimatedToLauncher
+                || launcherResumedThroughShellTransition
                 || forceOverviewInputConsumer) {
             return createOverviewInputConsumer(
-                    previousGestureState, gestureState, event, forceOverviewInputConsumer);
+                    previousGestureState,
+                    gestureState,
+                    event,
+                    forceOverviewInputConsumer,
+                    reasonString.append(SUBSTRING_PREFIX)
+                            .append(previousGestureAnimatedToLauncher
+                                    ? "previous gesture animated to launcher"
+                                    : (launcherResumedThroughShellTransition
+                                            ? "launcher resumed through a shell transition"
+                                            : "forceOverviewInputConsumer == true"))
+                            .append(", trying to use overview input consumer"));
         } else if (mDeviceState.isGestureBlockedTask(gestureState.getRunningTask())) {
-            return getDefaultInputConsumer();
+            return getDefaultInputConsumer(reasonString.append(SUBSTRING_PREFIX)
+                    .append("is gesture-blocked task, trying to use default input consumer"));
         } else {
+            reasonString.append(SUBSTRING_PREFIX)
+                    .append("using OtherActivityInputConsumer");
             return createOtherActivityInputConsumer(gestureState, event);
         }
     }
@@ -823,32 +1005,64 @@
                 mInputMonitorCompat, mInputEventReceiver, disableHorizontalSwipe, factory);
     }
 
-    private InputConsumer createDeviceLockedInputConsumer(GestureState gestureState) {
+    private InputConsumer createDeviceLockedInputConsumer(
+            GestureState gestureState, CompoundString reasonString) {
         if (mDeviceState.isFullyGesturalNavMode() && gestureState.getRunningTask() != null) {
-            return new DeviceLockedInputConsumer(this, mDeviceState, mTaskAnimationManager,
-                    gestureState, mInputMonitorCompat);
+            reasonString.append(SUBSTRING_PREFIX)
+                    .append("device is in gesture nav mode and running task != null")
+                    .append(", using DeviceLockedInputConsumer");
+            return new DeviceLockedInputConsumer(
+                    this, mDeviceState, mTaskAnimationManager, gestureState, mInputMonitorCompat);
         } else {
-            return getDefaultInputConsumer();
+            return getDefaultInputConsumer(reasonString
+                    .append(SUBSTRING_PREFIX)
+                    .append(mDeviceState.isFullyGesturalNavMode()
+                        ? "running task == null" : "device is not in gesture nav mode")
+                    .append(", trying to use default input consumer"));
         }
     }
 
-    public InputConsumer createOverviewInputConsumer(GestureState previousGestureState,
-            GestureState gestureState, MotionEvent event,
-            boolean forceOverviewInputConsumer) {
+    public InputConsumer createOverviewInputConsumer(
+            GestureState previousGestureState,
+            GestureState gestureState,
+            MotionEvent event,
+            boolean forceOverviewInputConsumer,
+            CompoundString reasonString) {
         StatefulActivity activity = gestureState.getActivityInterface().getCreatedActivity();
         if (activity == null) {
-            return getDefaultInputConsumer();
+            return getDefaultInputConsumer(
+                    reasonString.append(SUBSTRING_PREFIX)
+                            .append("activity == null, trying to use default input consumer"));
         }
 
-        if (activity.getRootView().hasWindowFocus()
-                || previousGestureState.isRunningAnimationToLauncher()
-                || (ASSISTANT_GIVES_LAUNCHER_FOCUS.get()
-                    && forceOverviewInputConsumer)
-                || (ENABLE_QUICKSTEP_LIVE_TILE.get()
-                && gestureState.getActivityInterface().isInLiveTileMode())) {
+        boolean hasWindowFocus = activity.getRootView().hasWindowFocus();
+        boolean isPreviousGestureAnimatingToLauncher =
+                previousGestureState.isRunningAnimationToLauncher();
+        boolean forcingOverviewInputConsumer =
+                ASSISTANT_GIVES_LAUNCHER_FOCUS.get() && forceOverviewInputConsumer;
+        boolean isInLiveTileMode = ENABLE_QUICKSTEP_LIVE_TILE.get()
+                && gestureState.getActivityInterface().isInLiveTileMode();
+        reasonString.append(SUBSTRING_PREFIX)
+                .append(hasWindowFocus
+                        ? "activity has window focus"
+                        : (isPreviousGestureAnimatingToLauncher
+                                ? "previous gesture is still animating to launcher"
+                                : (forcingOverviewInputConsumer
+                                        ? "assistant gives launcher focus and forcing focus"
+                                        : (isInLiveTileMode
+                                                ? "device is in live mode"
+                                                : "all overview focus conditions failed"))));
+        if (hasWindowFocus
+                || isPreviousGestureAnimatingToLauncher
+                || forcingOverviewInputConsumer
+                || isInLiveTileMode) {
+            reasonString.append(SUBSTRING_PREFIX)
+                    .append("overview should have focus, using OverviewInputConsumer");
             return new OverviewInputConsumer(gestureState, activity, mInputMonitorCompat,
                     false /* startingInActivityBounds */);
         } else {
+            reasonString.append(SUBSTRING_PREFIX).append(
+                    "overview shouldn't have focus, using OverviewWithoutFocusInputConsumer");
             final boolean disableHorizontalSwipe = mDeviceState.isInExclusionRegion(event);
             return new OverviewWithoutFocusInputConsumer(activity, mDeviceState, gestureState,
                     mInputMonitorCompat, disableHorizontalSwipe);
@@ -876,13 +1090,21 @@
         }
     }
 
+    private @NonNull InputConsumer getDefaultInputConsumer() {
+        return getDefaultInputConsumer(CompoundString.NO_OP);
+    }
+
     /**
      * Returns the {@link ResetGestureInputConsumer} if user is unlocked, else NO_OP.
      */
-    private @NonNull InputConsumer getDefaultInputConsumer() {
+    private @NonNull InputConsumer getDefaultInputConsumer(@NonNull CompoundString reasonString) {
         if (mResetGestureInputConsumer != null) {
+            reasonString.append(SUBSTRING_PREFIX).append(
+                    "mResetGestureInputConsumer initialized, using ResetGestureInputConsumer");
             return mResetGestureInputConsumer;
         } else {
+            reasonString.append(SUBSTRING_PREFIX).append(
+                    "mResetGestureInputConsumer not initialized, using no-op input consumer");
             // mResetGestureInputConsumer isn't initialized until onUserUnlocked(), so reset to
             // NO_OP until then (we never want these to be null).
             return InputConsumer.NO_OP;
@@ -997,15 +1219,18 @@
             pw.println("ProtoTrace:");
             pw.println("  file=" + ProtoTracer.INSTANCE.get(this).getTraceFile());
             if (createdOverviewActivity != null) {
-                createdOverviewActivity.getDeviceProfile().dump("", pw);
+                createdOverviewActivity.getDeviceProfile().dump(this, "", pw);
             }
             mTaskbarManager.dumpLogs("", pw);
+
+            ViewCapture.INSTANCE.get(this).dump(pw, fd);
         }
     }
 
     private void printAvailableCommands(PrintWriter pw) {
         pw.println("Available commands:");
         pw.println("  clear-touch-log: Clears the touch interaction log");
+        pw.println("  print-gesture-log: only prints the ActiveGestureLog dump");
     }
 
     private void onCommand(PrintWriter pw, LinkedList<String> args) {
@@ -1013,6 +1238,8 @@
             case "clear-touch-log":
                 ActiveGestureLog.INSTANCE.clear();
                 break;
+            case "print-gesture-log":
+                ActiveGestureLog.INSTANCE.dump("", pw);
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/ViewUtils.java b/quickstep/src/com/android/quickstep/ViewUtils.java
index 1fef544..b132067 100644
--- a/quickstep/src/com/android/quickstep/ViewUtils.java
+++ b/quickstep/src/com/android/quickstep/ViewUtils.java
@@ -17,6 +17,7 @@
 
 import android.graphics.HardwareRenderer;
 import android.os.Handler;
+import android.view.SurfaceControl;
 import android.view.View;
 import android.view.ViewRootImpl;
 
@@ -45,12 +46,15 @@
         return new FrameHandler(view, onFinishRunnable, canceled).schedule();
     }
 
-    private static class FrameHandler implements HardwareRenderer.FrameDrawingCallback {
+    private static class FrameHandler implements HardwareRenderer.FrameDrawingCallback,
+            ViewRootImpl.SurfaceChangedCallback {
 
         final ViewRootImpl mViewRoot;
         final Runnable mFinishCallback;
         final BooleanSupplier mCancelled;
         final Handler mHandler;
+        boolean mSurfaceCallbackRegistered = false;
+        boolean mFinished;
 
         int mDeferFrameCount = 1;
 
@@ -62,6 +66,23 @@
         }
 
         @Override
+        public void surfaceCreated(SurfaceControl.Transaction t) {
+            // Do nothing
+        }
+
+        @Override
+        public void surfaceReplaced(SurfaceControl.Transaction t) {
+            // Do nothing
+        }
+
+        @Override
+        public void surfaceDestroyed() {
+            // If the root view is detached, then the app won't get any scheduled frames so we need
+            // to force-run any pending callbacks
+            finish();
+        }
+
+        @Override
         public void onFrameDraw(long frame) {
             Utilities.postAsyncCallback(mHandler, this::onFrame);
         }
@@ -77,18 +98,35 @@
                 return;
             }
 
-            if (mFinishCallback != null) {
-                mFinishCallback.run();
-            }
+            finish();
         }
 
         private boolean schedule() {
             if (mViewRoot != null && mViewRoot.getView() != null) {
+                if (!mSurfaceCallbackRegistered) {
+                    mSurfaceCallbackRegistered = true;
+                    mViewRoot.addSurfaceChangedCallback(this);
+                }
                 mViewRoot.registerRtFrameCallback(this);
                 mViewRoot.getView().invalidate();
                 return true;
             }
             return false;
         }
+
+        private void finish() {
+            if (mFinished) {
+                return;
+            }
+            mFinished = true;
+            mDeferFrameCount = 0;
+            if (mFinishCallback != null) {
+                mFinishCallback.run();
+            }
+            if (mViewRoot != null) {
+                mViewRoot.removeSurfaceChangedCallback(this);
+                mSurfaceCallbackRegistered = false;
+            }
+        }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java b/quickstep/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
index 3e01ed0..8a87f63 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
@@ -22,7 +22,7 @@
 
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.DisplayController.NavigationMode;
+import com.android.launcher3.util.NavigationMode;
 import com.android.launcher3.util.TouchController;
 import com.android.quickstep.RecentsActivity;
 import com.android.quickstep.util.NavBarPosition;
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index f68bbbc..19a6c38 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -33,6 +33,7 @@
 import static com.android.quickstep.views.RecentsView.TASK_PRIMARY_SPLIT_TRANSLATION;
 import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_SPLIT_TRANSLATION;
 import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
+import static com.android.quickstep.views.RecentsView.TASK_THUMBNAIL_SPLASH_ALPHA;
 import static com.android.quickstep.views.TaskView.FLAG_UPDATE_ALL;
 
 import android.util.FloatProperty;
@@ -77,6 +78,11 @@
         }
         // While animating into recents, update the visible task data as needed
         setter.addOnFrameCallback(() -> mRecentsView.loadVisibleTaskData(FLAG_UPDATE_ALL));
+        setter.addEndListener(success -> {
+            if (!success) {
+                mRecentsView.reset();
+            }
+        });
         mRecentsView.updateEmptyMessage();
 
         setProperties(toState, config, setter);
@@ -105,14 +111,19 @@
         boolean showAsGrid = state.displayOverviewTasksAsGrid(mActivity.getDeviceProfile());
         setter.setFloat(mRecentsView, RECENTS_GRID_PROGRESS, showAsGrid ? 1f : 0f,
                 showAsGrid ? INSTANT : FINAL_FRAME);
+        setter.setFloat(mRecentsView, TASK_THUMBNAIL_SPLASH_ALPHA,
+                state.showTaskThumbnailSplash() ? 1f : 0f, INSTANT);
 
         setter.setViewBackgroundColor(mActivity.getScrimView(), state.getScrimColor(mActivity),
                 config.getInterpolator(ANIM_SCRIM_FADE, LINEAR));
 
         RecentsState currentState = mActivity.getStateManager().getState();
         if (isSplitSelectionState(state) && !isSplitSelectionState(currentState)) {
-            setter.add(mRecentsView.createSplitSelectInitAnimation(
-                    state.getTransitionDuration(mActivity, true /* isToState */)).buildAnim());
+            int duration = state.getTransitionDuration(mActivity, true /* isToState */);
+            // TODO (b/246851887): Pass in setter as a NO_ANIM PendingAnimation instead
+            PendingAnimation pa = new PendingAnimation(duration);
+            mRecentsView.createSplitSelectInitAnimation(pa, duration);
+            setter.add(pa.buildAnim());
         }
 
         Pair<FloatProperty, FloatProperty> taskViewsFloat =
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index ab3201a..e32aaee 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -15,7 +15,6 @@
  */
 package com.android.quickstep.fallback;
 
-import static com.android.launcher3.testing.TestProtocol.BAD_STATE;
 import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
 import static com.android.quickstep.fallback.RecentsState.DEFAULT;
 import static com.android.quickstep.fallback.RecentsState.HOME;
@@ -27,7 +26,6 @@
 import android.content.Context;
 import android.os.Build;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.MotionEvent;
 
 import androidx.annotation.Nullable;
@@ -35,6 +33,7 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.popup.QuickstepSystemShortcut;
 import com.android.launcher3.statemanager.StateManager.StateListener;
 import com.android.launcher3.util.SplitConfigurationOptions;
@@ -56,6 +55,8 @@
 public class FallbackRecentsView extends RecentsView<RecentsActivity, RecentsState>
         implements StateListener<RecentsState> {
 
+    private static final int TASK_DISMISS_DURATION = 150;
+
     @Nullable
     private Task mHomeTask;
 
@@ -107,8 +108,9 @@
         if (mHomeTask != null && endTarget == RECENTS && animatorSet != null) {
             TaskView tv = getTaskViewByTaskId(mHomeTask.key.id);
             if (tv != null) {
-                PendingAnimation pa = createTaskDismissAnimation(tv, true, false, 150,
-                        false /* dismissingForSplitSelection*/);
+                PendingAnimation pa = new PendingAnimation(TASK_DISMISS_DURATION);
+                createTaskDismissAnimation(pa, tv, true, false,
+                        TASK_DISMISS_DURATION, false /* dismissingForSplitSelection*/);
                 pa.addEndListener(e -> setCurrentTask(-1));
                 AnimatorPlaybackController controller = pa.createPlaybackController();
                 controller.dispatchOnStart();
@@ -212,8 +214,9 @@
 
     @Override
     public void initiateSplitSelect(TaskView taskView,
-            @SplitConfigurationOptions.StagePosition int stagePosition) {
-        super.initiateSplitSelect(taskView, stagePosition);
+            @SplitConfigurationOptions.StagePosition int stagePosition,
+            StatsLogManager.EventEnum splitEvent) {
+        super.initiateSplitSelect(taskView, stagePosition, splitEvent);
         mActivity.getStateManager().goToState(OVERVIEW_SPLIT_SELECT);
     }
 
@@ -225,7 +228,6 @@
         if (toState == MODAL_TASK) {
             setOverviewSelectEnabled(true);
         }
-        Log.d(BAD_STATE, "FRV onStateTransitionStart setFreezeVisibility=true, toState=" + toState);
         setFreezeViewVisibility(true);
     }
 
@@ -237,8 +239,6 @@
         }
         boolean isOverlayEnabled = finalState == DEFAULT || finalState == MODAL_TASK;
         setOverlayEnabled(isOverlayEnabled);
-        Log.d(BAD_STATE, "FRV onStateTransitionComplete setFreezeVisibility=false, finalState="
-                + finalState);
         setFreezeViewVisibility(false);
         if (finalState != MODAL_TASK) {
             setOverviewSelectEnabled(false);
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsState.java b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
index 77db6b7..223eba5 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;
 
@@ -40,6 +41,7 @@
     private static final int FLAG_SCRIM = BaseState.getFlag(5);
     private static final int FLAG_LIVE_TILE = BaseState.getFlag(6);
     private static final int FLAG_OVERVIEW_UI = BaseState.getFlag(7);
+    private static final int FLAG_TASK_THUMBNAIL_SPLASH = BaseState.getFlag(8);
 
     public static final RecentsState DEFAULT = new RecentsState(0,
             FLAG_DISABLE_RESTORE | FLAG_CLEAR_ALL_BUTTON | FLAG_OVERVIEW_ACTIONS | FLAG_SHOW_AS_GRID
@@ -48,11 +50,12 @@
             FLAG_DISABLE_RESTORE | FLAG_CLEAR_ALL_BUTTON | FLAG_OVERVIEW_ACTIONS | FLAG_MODAL
                     | FLAG_SHOW_AS_GRID | FLAG_SCRIM | FLAG_LIVE_TILE | FLAG_OVERVIEW_UI);
     public static final RecentsState BACKGROUND_APP = new BackgroundAppState(2,
-            FLAG_DISABLE_RESTORE | FLAG_NON_INTERACTIVE | FLAG_FULL_SCREEN | FLAG_OVERVIEW_UI);
+            FLAG_DISABLE_RESTORE | FLAG_NON_INTERACTIVE | FLAG_FULL_SCREEN | FLAG_OVERVIEW_UI
+                    | FLAG_TASK_THUMBNAIL_SPLASH);
     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;
@@ -138,6 +141,11 @@
         return hasFlag(FLAG_SHOW_AS_GRID) && deviceProfile.isTablet;
     }
 
+    @Override
+    public boolean showTaskThumbnailSplash() {
+        return hasFlag(FLAG_TASK_THUMBNAIL_SPLASH);
+    }
+
     /**
      * True if the state has overview panel visible.
      */
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
index 8da2fd3..03f8eef 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
@@ -3,7 +3,7 @@
 import android.view.MotionEvent;
 
 import com.android.launcher3.testing.TestLogging;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.tracing.InputConsumerProto;
 import com.android.quickstep.InputConsumer;
 import com.android.systemui.shared.system.InputMonitorCompat;
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
index 3d737ca..6bc24f2 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;
@@ -41,7 +42,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.testing.TestLogging;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.util.DisplayController;
 import com.android.quickstep.AnimatedFloat;
 import com.android.quickstep.GestureState;
@@ -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/OneHandedModeInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
index bc20902..d7ed79b 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
@@ -21,7 +21,7 @@
 import static android.view.MotionEvent.ACTION_MOVE;
 import static android.view.MotionEvent.ACTION_UP;
 
-import static com.android.launcher3.ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE;
+import static com.android.launcher3.testing.shared.ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE;
 import static com.android.launcher3.Utilities.squaredHypot;
 
 import android.content.Context;
@@ -30,7 +30,7 @@
 import android.view.MotionEvent;
 
 import com.android.launcher3.R;
-import com.android.launcher3.ResourceUtils;
+import com.android.launcher3.testing.shared.ResourceUtils;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.DisplayController;
 import com.android.quickstep.InputConsumer;
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index e458c1f..7ccd8af 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -27,8 +27,10 @@
 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.ActiveGestureErrorDetector.GestureEvent.START_RECENTS_ANIMATION;
 import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
 
 import android.annotation.TargetApi;
@@ -47,8 +49,9 @@
 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.testing.shared.TestProtocol;
 import com.android.launcher3.tracing.InputConsumerProto;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.TraceHelper;
@@ -58,7 +61,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 +112,7 @@
     private VelocityTracker mVelocityTracker;
 
     private AbsSwipeUpHandler mInteractionHandler;
+    private final FinishImmediatelyHandler mCleanupHandler = new FinishImmediatelyHandler();
 
     private final boolean mIsDeferredDownTarget;
     private final PointF mDownPos = new PointF();
@@ -354,7 +360,6 @@
     }
 
     private void notifyGestureStarted(boolean isLikelyToStartNewTask) {
-        ActiveGestureLog.INSTANCE.addLog("startQuickstep");
         if (mInteractionHandler == null) {
             return;
         }
@@ -368,7 +373,9 @@
     }
 
     private void startTouchTrackingForWindowAnimation(long touchTimeMs) {
-        ActiveGestureLog.INSTANCE.addLog("startRecentsAnimation");
+        ActiveGestureLog.INSTANCE.addLog(
+                /* event= */ "startRecentsAnimation",
+                /* gestureEvent= */ START_RECENTS_ANIMATION);
 
         mInteractionHandler = mHandlerFactory.newHandler(mGestureState, touchTimeMs);
         mInteractionHandler.setGestureEndCallback(this::onInteractionGestureFinished);
@@ -377,6 +384,7 @@
 
         if (mTaskAnimationManager.isRecentsAnimationRunning()) {
             mActiveCallbacks = mTaskAnimationManager.continueRecentsAnimation(mGestureState);
+            mActiveCallbacks.removeListener(mCleanupHandler);
             mActiveCallbacks.addListener(mInteractionHandler);
             mTaskAnimationManager.notifyRecentsAnimationState(mInteractionHandler);
             notifyGestureStarted(true /*isLikelyToStartNewTask*/);
@@ -414,7 +422,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 +484,7 @@
     }
 
     private void removeListener() {
-        if (mActiveCallbacks != null) {
+        if (mActiveCallbacks != null && mInteractionHandler != null) {
             mActiveCallbacks.removeListener(mInteractionHandler);
         }
     }
@@ -490,4 +510,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/OverviewInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
index 02ac48e..6f35928 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
@@ -29,13 +29,12 @@
 import com.android.launcher3.statemanager.BaseState;
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.testing.TestLogging;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.quickstep.BaseActivityInterface;
 import com.android.quickstep.GestureState;
 import com.android.quickstep.InputConsumer;
 import com.android.quickstep.TaskUtils;
-import com.android.quickstep.util.ActiveGestureLog;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
 /**
@@ -91,7 +90,6 @@
             if (!mStartingInActivityBounds) {
                 mActivityInterface.closeOverlay();
                 TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
-                ActiveGestureLog.INSTANCE.addLog("startQuickstep");
             }
             if (mInputMonitor != null) {
                 TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers");
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
index 864e08d..b70fe8e 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;
@@ -29,11 +28,10 @@
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.testing.TestLogging;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.quickstep.GestureState;
 import com.android.quickstep.InputConsumer;
 import com.android.quickstep.RecentsAnimationDeviceState;
-import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.TriggerSwipeUpTouchTracker;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
@@ -79,12 +77,7 @@
 
     @Override
     public void onSwipeUp(boolean wasFling, PointF finalVelocity) {
-        try {
-            mContext.startActivity(mGestureState.getHomeIntent());
-        } catch (NullPointerException | ActivityNotFoundException | SecurityException e) {
-            mContext.startActivity(createHomeIntent());
-        }
-        ActiveGestureLog.INSTANCE.addLog("startQuickstep");
+        startHomeIntentSafely(mContext, mGestureState.getHomeIntent(), null);
         BaseActivity activity = BaseDraggingActivity.fromContext(mContext);
         int state = (mGestureState != null && mGestureState.getEndTarget() != null)
                 ? mGestureState.getEndTarget().containerType
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java
index 71dca66..b9b5e7c 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java
@@ -28,9 +28,11 @@
 import android.graphics.Point;
 import android.view.MotionEvent;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.anim.AnimatorListeners;
 import com.android.launcher3.testing.TestLogging;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.touch.SingleAxisSwipeDetector;
 import com.android.launcher3.util.DisplayController;
 import com.android.quickstep.AnimatedFloat;
@@ -41,6 +43,7 @@
 import com.android.quickstep.RecentsAnimationController;
 import com.android.quickstep.RecentsAnimationTargets;
 import com.android.quickstep.TaskAnimationManager;
+import com.android.quickstep.util.ActiveGestureErrorDetector;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
@@ -99,7 +102,8 @@
         mDisplaySize = DisplayController.INSTANCE.get(context).getInfo().currentSize;
 
         // Init states
-        mStateCallback = new MultiStateCallback(STATE_NAMES);
+        mStateCallback = new MultiStateCallback(
+                STATE_NAMES, ProgressDelegateInputConsumer::getTrackedEventForState);
         mStateCallback.runOnceAtState(STATE_TARGET_RECEIVED | STATE_HANDLER_INVALIDATED,
                 this::endRemoteAnimation);
         mStateCallback.runOnceAtState(STATE_TARGET_RECEIVED | STATE_FLING_FINISHED,
@@ -109,6 +113,14 @@
         mSwipeDetector.setDetectableScrollConditions(DIRECTION_POSITIVE, false);
     }
 
+    @Nullable
+    private static ActiveGestureErrorDetector.GestureEvent getTrackedEventForState(int stateFlag) {
+        if (stateFlag == STATE_HANDLER_INVALIDATED) {
+            return ActiveGestureErrorDetector.GestureEvent.STATE_HANDLER_INVALIDATED;
+        }
+        return null;
+    }
+
     @Override
     public int getType() {
         return TYPE_PROGRESS_DELEGATE;
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java
index d34b40b..349f4d2 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java
@@ -17,18 +17,25 @@
 
 import android.view.MotionEvent;
 
+import com.android.launcher3.taskbar.TaskbarActivityContext;
 import com.android.quickstep.InputConsumer;
 import com.android.quickstep.TaskAnimationManager;
 
+import java.util.function.Supplier;
+
 /**
  * A NO_OP input consumer which also resets any pending gesture
  */
 public class ResetGestureInputConsumer implements InputConsumer {
 
     private final TaskAnimationManager mTaskAnimationManager;
+    private final Supplier<TaskbarActivityContext> mActivityContextSupplier;
 
-    public ResetGestureInputConsumer(TaskAnimationManager taskAnimationManager) {
+    public ResetGestureInputConsumer(
+            TaskAnimationManager taskAnimationManager,
+            Supplier<TaskbarActivityContext> activityContextSupplier) {
         mTaskAnimationManager = taskAnimationManager;
+        mActivityContextSupplier = activityContextSupplier;
     }
 
     @Override
@@ -40,7 +47,9 @@
     public void onMotionEvent(MotionEvent ev) {
         if (ev.getAction() == MotionEvent.ACTION_DOWN
                 && mTaskAnimationManager.isRecentsAnimationRunning()) {
-            mTaskAnimationManager.finishRunningRecentsAnimation(false /* toHome */);
+            TaskbarActivityContext tac = mActivityContextSupplier.get();
+            mTaskAnimationManager.finishRunningRecentsAnimation(
+                    /* toHome= */ tac != null && !tac.isInApp());
         }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java
index 878f132..4806ac1 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java
@@ -23,7 +23,7 @@
 import android.view.MotionEvent;
 
 import com.android.launcher3.testing.TestLogging;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.quickstep.InputConsumer;
 import com.android.quickstep.RecentsAnimationDeviceState;
 import com.android.quickstep.util.TriggerSwipeUpTouchTracker;
diff --git a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
index 47bb3cf..8ad17cb 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;
@@ -154,45 +155,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
@@ -302,7 +308,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/interaction/EdgeBackGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java
index d059d82..8660d87 100644
--- a/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java
+++ b/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java
@@ -29,7 +29,7 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.ResourceUtils;
+import com.android.launcher3.testing.shared.ResourceUtils;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.DisplayController;
 
diff --git a/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java b/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java
index b2b2f59..437572b 100644
--- a/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java
+++ b/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java
@@ -41,7 +41,7 @@
 import androidx.dynamicanimation.animation.SpringForce;
 
 import com.android.launcher3.R;
-import com.android.launcher3.ResourceUtils;
+import com.android.launcher3.testing.shared.ResourceUtils;
 import com.android.launcher3.anim.Interpolators;
 import com.android.quickstep.util.VibratorWrapper;
 
diff --git a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
index f981860..e7bf7e2 100644
--- a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
+++ b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
@@ -44,10 +44,10 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.R;
-import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.testing.shared.ResourceUtils;
 import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.DisplayController.NavigationMode;
+import com.android.launcher3.util.NavigationMode;
 import com.android.quickstep.util.MotionPauseDetector;
 import com.android.quickstep.util.NavBarPosition;
 import com.android.quickstep.util.TriggerSwipeUpTouchTracker;
diff --git a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
index b70c411..fa7bc04 100644
--- a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
@@ -358,7 +358,7 @@
             };
             RectFSpringAnim windowAnim = createWindowAnimationToHome(startShift,
                     homeAnimFactory)[0];
-            windowAnim.start(mContext, velocityPxPerMs);
+            windowAnim.start(mContext, mDp, velocityPxPerMs);
             return windowAnim;
         }
     }
diff --git a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
index bd0250d..2ccdfa3 100644
--- a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
+++ b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
@@ -40,15 +40,14 @@
 
 import com.android.launcher3.AutoInstallsLayout;
 import com.android.launcher3.R;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.logging.StatsLogManager.StatsLogger;
 import com.android.launcher3.model.DeviceGridState;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.Info;
-import com.android.launcher3.util.DisplayController.NavigationMode;
 import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.NavigationMode;
 import com.android.launcher3.util.SettingsCache;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -179,11 +178,9 @@
                 logger::log);
 
         SharedPreferences prefs = getPrefs(mContext);
-        if (FeatureFlags.ENABLE_THEMED_ICONS.get()) {
-            logger.log(prefs.getBoolean(KEY_THEMED_ICONS, false)
-                    ? LAUNCHER_THEMED_ICON_ENABLED
-                    : LAUNCHER_THEMED_ICON_DISABLED);
-        }
+        logger.log(prefs.getBoolean(KEY_THEMED_ICONS, false)
+                ? LAUNCHER_THEMED_ICON_ENABLED
+                : LAUNCHER_THEMED_ICON_DISABLED);
 
         mLoggablePrefs.forEach((key, lp) -> logger.log(() ->
                 prefs.getBoolean(key, lp.defaultValue) ? lp.eventIdOn : lp.eventIdOff));
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index 45c8036..37a28e5 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -336,8 +336,9 @@
                 appState.getModel().enqueueModelUpdateTask(
                         new BaseModelUpdateTask() {
                             @Override
-                            public void execute(LauncherAppState app, BgDataModel dataModel,
-                                    AllAppsList apps) {
+                            public void execute(@NonNull final LauncherAppState app,
+                                    @NonNull final BgDataModel dataModel,
+                                    @NonNull final AllAppsList apps) {
                                 FolderInfo folderInfo = dataModel.folders.get(mItemInfo.container);
                                 write(event, applyOverwrites(mItemInfo.buildProto(folderInfo)));
                             }
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
new file mode 100644
index 0000000..53e0c2b
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
@@ -0,0 +1,330 @@
+/*
+ * 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.util.ArraySet;
+
+import androidx.annotation.NonNull;
+
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Utility class for tracking gesture navigation events as they happen, then detecting and reporting
+ * known issues at log dump time.
+ */
+public class ActiveGestureErrorDetector {
+
+    /**
+     * Enums associated to gesture navigation events.
+     */
+    public enum GestureEvent {
+        MOTION_DOWN, MOTION_UP, SET_END_TARGET, SET_END_TARGET_HOME, SET_END_TARGET_LAST_TASK,
+        SET_END_TARGET_NEW_TASK, ON_SETTLED_ON_END_TARGET, START_RECENTS_ANIMATION,
+        FINISH_RECENTS_ANIMATION, CANCEL_RECENTS_ANIMATION, SET_ON_PAGE_TRANSITION_END_CALLBACK,
+        CANCEL_CURRENT_ANIMATION, CLEANUP_SCREENSHOT, SCROLLER_ANIMATION_ABORTED, TASK_APPEARED,
+
+        /**
+         * These GestureEvents are specifically associated to state flags that get set in
+         * {@link com.android.quickstep.MultiStateCallback}. If a state flag needs to be tracked
+         * for error detection, an enum should be added here and that state flag-enum pair should
+         * be added to the state flag's container class' {@code getTrackedEventForState} method.
+         */
+        STATE_GESTURE_STARTED, STATE_GESTURE_COMPLETED, STATE_GESTURE_CANCELLED,
+        STATE_END_TARGET_ANIMATION_FINISHED, STATE_RECENTS_SCROLLING_FINISHED,
+        STATE_CAPTURE_SCREENSHOT, STATE_SCREENSHOT_CAPTURED, STATE_HANDLER_INVALIDATED,
+        STATE_RECENTS_ANIMATION_CANCELED, STATE_LAUNCHER_DRAWN(true, false);
+
+        public final boolean mLogEvent;
+        public final boolean mTrackEvent;
+
+        GestureEvent() {
+            this(false, true);
+        }
+
+        GestureEvent(boolean logEvent, boolean trackEvent) {
+            mLogEvent = logEvent;
+            mTrackEvent = trackEvent;
+        }
+    }
+
+    private ActiveGestureErrorDetector() {}
+
+    protected static void analyseAndDump(
+            @NonNull String prefix,
+            @NonNull PrintWriter writer,
+            List<ActiveGestureLog.EventLog> eventLogs) {
+        writer.println(prefix + "ActiveGestureErrorDetector:");
+        for (int i = 0; i < eventLogs.size(); i++) {
+            ActiveGestureLog.EventLog eventLog = eventLogs.get(i);
+            if (eventLog == null) {
+                continue;
+            }
+            int gestureId = eventLog.logId;
+            writer.println(prefix + "\tError messages for gesture ID: " + gestureId);
+
+            boolean errorDetected = false;
+            // Use a Set since the order is inherently checked in the loop.
+            final Set<GestureEvent> encounteredEvents = new ArraySet<>();
+            // Set flags and check order of operations.
+            for (ActiveGestureLog.EventEntry eventEntry : eventLog.eventEntries) {
+                GestureEvent gestureEvent = eventEntry.getGestureEvent();
+                if (gestureEvent == null) {
+                    continue;
+                }
+                encounteredEvents.add(gestureEvent);
+                switch (gestureEvent) {
+                    case MOTION_UP:
+                        errorDetected |= printErrorIfTrue(
+                                !encounteredEvents.contains(GestureEvent.MOTION_DOWN),
+                                /* errorMessage= */ prefix + "\t\tMotion up detected before/without"
+                                        + " motion down.",
+                                writer);
+                        break;
+                    case ON_SETTLED_ON_END_TARGET:
+                        errorDetected |= printErrorIfTrue(
+                                !encounteredEvents.contains(GestureEvent.SET_END_TARGET),
+                                /* errorMessage= */ prefix + "\t\tonSettledOnEndTarget called "
+                                        + "before/without setEndTarget.",
+                                writer);
+                        break;
+                    case FINISH_RECENTS_ANIMATION:
+                        errorDetected |= printErrorIfTrue(
+                                !encounteredEvents.contains(GestureEvent.START_RECENTS_ANIMATION),
+                                /* errorMessage= */ prefix + "\t\tfinishRecentsAnimation called "
+                                        + "before/without startRecentsAnimation.",
+                                writer);
+                        break;
+                    case CANCEL_RECENTS_ANIMATION:
+                        errorDetected |= printErrorIfTrue(
+                                !encounteredEvents.contains(GestureEvent.START_RECENTS_ANIMATION),
+                                /* errorMessage= */ prefix + "\t\tcancelRecentsAnimation called "
+                                        + "before/without startRecentsAnimation.",
+                                writer);
+                        break;
+                    case CLEANUP_SCREENSHOT:
+                        errorDetected |= printErrorIfTrue(
+                                !encounteredEvents.contains(GestureEvent.STATE_SCREENSHOT_CAPTURED),
+                                /* errorMessage= */ prefix + "\t\trecents activity screenshot was "
+                                        + "cleaned up before/without STATE_SCREENSHOT_CAPTURED "
+                                        + "being set.",
+                                writer);
+                        break;
+                    case SCROLLER_ANIMATION_ABORTED:
+                        errorDetected |= printErrorIfTrue(
+                                encounteredEvents.contains(GestureEvent.SET_END_TARGET_HOME)
+                                        && !encounteredEvents.contains(
+                                                GestureEvent.ON_SETTLED_ON_END_TARGET),
+                                /* errorMessage= */ prefix + "\t\trecents view scroller animation "
+                                        + "aborted after setting end target HOME, but before"
+                                        + " settling on end target.",
+                                writer);
+                        break;
+                    case TASK_APPEARED:
+                        errorDetected |= printErrorIfTrue(
+                                !encounteredEvents.contains(GestureEvent.SET_END_TARGET_LAST_TASK)
+                                        && !encounteredEvents.contains(
+                                        GestureEvent.SET_END_TARGET_NEW_TASK),
+                                /* errorMessage= */ prefix + "\t\tonTasksAppeared called "
+                                        + "before/without setting end target to last or new task",
+                                writer);
+                        break;
+                    case STATE_GESTURE_COMPLETED:
+                        errorDetected |= printErrorIfTrue(
+                                !encounteredEvents.contains(GestureEvent.MOTION_UP),
+                                /* errorMessage= */ prefix + "\t\tSTATE_GESTURE_COMPLETED set "
+                                        + "before/without motion up.",
+                                writer);
+                        errorDetected |= printErrorIfTrue(
+                                !encounteredEvents.contains(GestureEvent.STATE_GESTURE_STARTED),
+                                /* errorMessage= */ prefix + "\t\tSTATE_GESTURE_COMPLETED set "
+                                        + "before/without STATE_GESTURE_STARTED.",
+                                writer);
+                        break;
+                    case STATE_GESTURE_CANCELLED:
+                        errorDetected |= printErrorIfTrue(
+                                !encounteredEvents.contains(GestureEvent.MOTION_UP),
+                                /* errorMessage= */ prefix + "\t\tSTATE_GESTURE_CANCELLED set "
+                                        + "before/without motion up.",
+                                writer);
+                        errorDetected |= printErrorIfTrue(
+                                !encounteredEvents.contains(GestureEvent.STATE_GESTURE_STARTED),
+                                /* errorMessage= */ prefix + "\t\tSTATE_GESTURE_CANCELLED set "
+                                        + "before/without STATE_GESTURE_STARTED.",
+                                writer);
+                        break;
+                    case STATE_SCREENSHOT_CAPTURED:
+                        errorDetected |= printErrorIfTrue(
+                                !encounteredEvents.contains(GestureEvent.STATE_CAPTURE_SCREENSHOT),
+                                /* errorMessage= */ prefix + "\t\tSTATE_SCREENSHOT_CAPTURED set "
+                                        + "before/without STATE_CAPTURE_SCREENSHOT.",
+                                writer);
+                        break;
+                    case STATE_RECENTS_SCROLLING_FINISHED:
+                        errorDetected |= printErrorIfTrue(
+                                !encounteredEvents.contains(
+                                        GestureEvent.SET_ON_PAGE_TRANSITION_END_CALLBACK),
+                                /* errorMessage= */ prefix + "\t\tSTATE_RECENTS_SCROLLING_FINISHED "
+                                        + "set before/without calling "
+                                        + "setOnPageTransitionEndCallback.",
+                                writer);
+                        break;
+                    case STATE_RECENTS_ANIMATION_CANCELED:
+                        errorDetected |= printErrorIfTrue(
+                                !encounteredEvents.contains(
+                                        GestureEvent.START_RECENTS_ANIMATION),
+                                /* errorMessage= */ prefix + "\t\tSTATE_RECENTS_ANIMATION_CANCELED "
+                                        + "set before/without startRecentsAnimation.",
+                                writer);
+                        break;
+                    case MOTION_DOWN:
+                    case SET_END_TARGET:
+                    case SET_END_TARGET_HOME:
+                    case START_RECENTS_ANIMATION:
+                    case SET_ON_PAGE_TRANSITION_END_CALLBACK:
+                    case CANCEL_CURRENT_ANIMATION:
+                    case STATE_GESTURE_STARTED:
+                    case STATE_END_TARGET_ANIMATION_FINISHED:
+                    case STATE_CAPTURE_SCREENSHOT:
+                    case STATE_HANDLER_INVALIDATED:
+                    case STATE_LAUNCHER_DRAWN:
+                    default:
+                        // No-Op
+                }
+            }
+
+            // Check that all required events were found.
+            errorDetected |= printErrorIfTrue(
+                    !encounteredEvents.contains(GestureEvent.MOTION_DOWN),
+                    /* errorMessage= */ prefix + "\t\tMotion down never detected.",
+                    writer);
+            errorDetected |= printErrorIfTrue(
+                    !encounteredEvents.contains(GestureEvent.MOTION_UP),
+                    /* errorMessage= */ prefix + "\t\tMotion up never detected.",
+                    writer);
+
+            errorDetected |= printErrorIfTrue(
+                    /* condition= */ encounteredEvents.contains(GestureEvent.SET_END_TARGET)
+                            && !encounteredEvents.contains(GestureEvent.ON_SETTLED_ON_END_TARGET),
+                    /* errorMessage= */ prefix + "\t\tsetEndTarget was called, but "
+                            + "onSettledOnEndTarget wasn't.",
+                    writer);
+            errorDetected |= printErrorIfTrue(
+                    /* condition= */ encounteredEvents.contains(GestureEvent.SET_END_TARGET)
+                            && !encounteredEvents.contains(
+                                    GestureEvent.STATE_END_TARGET_ANIMATION_FINISHED),
+                    /* errorMessage= */ prefix + "\t\tsetEndTarget was called, but "
+                            + "STATE_END_TARGET_ANIMATION_FINISHED was never set.",
+                    writer);
+            errorDetected |= printErrorIfTrue(
+                    /* condition= */ encounteredEvents.contains(GestureEvent.SET_END_TARGET)
+                            && !encounteredEvents.contains(
+                                    GestureEvent.STATE_RECENTS_SCROLLING_FINISHED),
+                    /* errorMessage= */ prefix + "\t\tsetEndTarget was called, but "
+                            + "STATE_RECENTS_SCROLLING_FINISHED was never set.",
+                    writer);
+            errorDetected |= printErrorIfTrue(
+                    /* condition= */ encounteredEvents.contains(
+                            GestureEvent.STATE_END_TARGET_ANIMATION_FINISHED)
+                            && encounteredEvents.contains(
+                                    GestureEvent.STATE_RECENTS_SCROLLING_FINISHED)
+                            && !encounteredEvents.contains(GestureEvent.ON_SETTLED_ON_END_TARGET),
+                    /* errorMessage= */ prefix + "\t\tSTATE_END_TARGET_ANIMATION_FINISHED and "
+                            + "STATE_RECENTS_SCROLLING_FINISHED were set, but onSettledOnEndTarget "
+                            + "wasn't called.",
+                    writer);
+
+            errorDetected |= printErrorIfTrue(
+                    /* condition= */ encounteredEvents.contains(
+                            GestureEvent.START_RECENTS_ANIMATION)
+                            && !encounteredEvents.contains(GestureEvent.FINISH_RECENTS_ANIMATION)
+                            && !encounteredEvents.contains(GestureEvent.CANCEL_RECENTS_ANIMATION),
+                    /* errorMessage= */ prefix + "\t\tstartRecentsAnimation was called, but "
+                            + "finishRecentsAnimation and cancelRecentsAnimation weren't.",
+                    writer);
+
+            errorDetected |= printErrorIfTrue(
+                    /* condition= */ encounteredEvents.contains(GestureEvent.STATE_GESTURE_STARTED)
+                            && !encounteredEvents.contains(GestureEvent.STATE_GESTURE_COMPLETED)
+                            && !encounteredEvents.contains(GestureEvent.STATE_GESTURE_CANCELLED),
+                    /* errorMessage= */ prefix + "\t\tSTATE_GESTURE_STARTED was set, but "
+                            + "STATE_GESTURE_COMPLETED and STATE_GESTURE_CANCELLED weren't.",
+                    writer);
+
+            errorDetected |= printErrorIfTrue(
+                    /* condition= */ encounteredEvents.contains(
+                            GestureEvent.STATE_CAPTURE_SCREENSHOT)
+                            && !encounteredEvents.contains(GestureEvent.STATE_SCREENSHOT_CAPTURED),
+                    /* errorMessage= */ prefix + "\t\tSTATE_CAPTURE_SCREENSHOT was set, but "
+                            + "STATE_SCREENSHOT_CAPTURED wasn't.",
+                    writer);
+
+            errorDetected |= printErrorIfTrue(
+                    /* condition= */ encounteredEvents.contains(
+                            GestureEvent.SET_ON_PAGE_TRANSITION_END_CALLBACK)
+                            && !encounteredEvents.contains(
+                                    GestureEvent.STATE_RECENTS_SCROLLING_FINISHED),
+                    /* errorMessage= */ prefix + "\t\tsetOnPageTransitionEndCallback called, but "
+                            + "STATE_RECENTS_SCROLLING_FINISHED wasn't set.",
+                    writer);
+
+            errorDetected |= printErrorIfTrue(
+                    /* condition= */ !encounteredEvents.contains(
+                            GestureEvent.CANCEL_CURRENT_ANIMATION)
+                            && !encounteredEvents.contains(GestureEvent.STATE_HANDLER_INVALIDATED),
+                    /* errorMessage= */ prefix + "\t\tAbsSwipeUpHandler.cancelCurrentAnimation "
+                            + "wasn't called and STATE_HANDLER_INVALIDATED wasn't set.",
+                    writer);
+
+            errorDetected |= printErrorIfTrue(
+                    /* condition= */ encounteredEvents.contains(
+                            GestureEvent.STATE_RECENTS_ANIMATION_CANCELED)
+                            && !encounteredEvents.contains(GestureEvent.CLEANUP_SCREENSHOT),
+                    /* errorMessage= */ prefix + "\t\tSTATE_RECENTS_ANIMATION_CANCELED was set but "
+                            + "the task screenshot wasn't cleaned up.",
+                    writer);
+
+            errorDetected |= printErrorIfTrue(
+                    /* condition= */ encounteredEvents.contains(
+                            GestureEvent.SET_END_TARGET_LAST_TASK)
+                            && !encounteredEvents.contains(GestureEvent.TASK_APPEARED),
+                    /* errorMessage= */ prefix + "\t\tend target set to last task, but "
+                            + "onTaskAppeared wasn't called.",
+                    writer);
+            errorDetected |= printErrorIfTrue(
+                    /* condition= */ encounteredEvents.contains(
+                            GestureEvent.SET_END_TARGET_NEW_TASK)
+                            && !encounteredEvents.contains(GestureEvent.TASK_APPEARED),
+                    /* errorMessage= */ prefix + "\t\tend target set to new task, but "
+                            + "onTaskAppeared wasn't called.",
+                    writer);
+
+            if (!errorDetected) {
+                writer.println(prefix + "\t\tNo errors detected.");
+            }
+        }
+    }
+
+    private static boolean printErrorIfTrue(
+            boolean condition, String errorMessage, PrintWriter writer) {
+        if (!condition) {
+            return false;
+        }
+        writer.println(errorMessage);
+        return true;
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
index fabfc4b..23fdd58 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
@@ -15,15 +15,26 @@
  */
 package com.android.quickstep.util;
 
-import android.content.Context;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
-import com.android.launcher3.logging.EventLogArray;
-import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.config.FeatureFlags;
+
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
 
 /**
  * A log to keep track of the active gesture.
  */
-public class ActiveGestureLog extends EventLogArray {
+public class ActiveGestureLog {
+
+    private static final int MAX_GESTURES_TRACKED = 10;
 
     public static final ActiveGestureLog INSTANCE = new ActiveGestureLog();
 
@@ -33,7 +44,306 @@
      */
     public static final String INTENT_EXTRA_LOG_TRACE_ID = "INTENT_EXTRA_LOG_TRACE_ID";
 
+    private static final int TYPE_ONE_OFF = 0;
+    private static final int TYPE_FLOAT = 1;
+    private static final int TYPE_INTEGER = 2;
+    private static final int TYPE_BOOL_TRUE = 3;
+    private static final int TYPE_BOOL_FALSE = 4;
+    private static final int TYPE_INPUT_CONSUMER = 5;
+    private static final int TYPE_GESTURE_EVENT = 6;
+
+    private final EventLog[] logs;
+    private int nextIndex;
+    private int mCurrentLogId = 100;
+
     private ActiveGestureLog() {
-        super("touch_interaction_log", 40);
+        this.logs = new EventLog[MAX_GESTURES_TRACKED];
+        this.nextIndex = 0;
+    }
+
+    /**
+     * Track the given event for error detection.
+     *
+     * @param gestureEvent GestureEvent representing an event during the current gesture's
+     *                   execution.
+     */
+    public void trackEvent(@Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
+        addLog(TYPE_GESTURE_EVENT, "", 0, CompoundString.NO_OP, gestureEvent);
+    }
+
+    public void addLog(String event) {
+        addLog(event, null);
+    }
+
+    public void addLog(String event, int extras) {
+        addLog(event, extras, null);
+    }
+
+    public void addLog(String event, boolean extras) {
+        addLog(event, extras, null);
+    }
+
+    public void addLog(CompoundString compoundString) {
+        addLog(TYPE_INPUT_CONSUMER, "", 0, compoundString, null);
+    }
+
+    /**
+     * Adds a log and track the associated event for error detection.
+     *
+     * @param gestureEvent GestureEvent representing the event being logged.
+     */
+    public void addLog(
+            String event, @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
+        addLog(TYPE_ONE_OFF, event, 0, CompoundString.NO_OP, gestureEvent);
+    }
+
+    public void addLog(
+            String event,
+            int extras,
+            @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
+        addLog(TYPE_INTEGER, event, extras, CompoundString.NO_OP, gestureEvent);
+    }
+
+    public void addLog(
+            String event,
+            boolean extras,
+            @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
+        addLog(
+                extras ? TYPE_BOOL_TRUE : TYPE_BOOL_FALSE,
+                event,
+                0,
+                CompoundString.NO_OP,
+                gestureEvent);
+    }
+
+    private void addLog(
+            int type,
+            String event,
+            float extras,
+            CompoundString compoundString,
+            @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
+        EventLog lastEventLog = logs[(nextIndex + logs.length - 1) % logs.length];
+        if (lastEventLog == null || mCurrentLogId != lastEventLog.logId) {
+            EventLog eventLog = new EventLog(mCurrentLogId);
+            EventEntry eventEntry = new EventEntry();
+
+            eventEntry.update(type, event, extras, compoundString, gestureEvent);
+            eventLog.eventEntries.add(eventEntry);
+            logs[nextIndex] = eventLog;
+            nextIndex = (nextIndex + 1) % logs.length;
+            return;
+        }
+
+        // Update the last EventLog
+        List<EventEntry> lastEventEntries = lastEventLog.eventEntries;
+        EventEntry lastEntry = lastEventEntries.size() > 0
+                ? lastEventEntries.get(lastEventEntries.size() - 1) : null;
+
+        // Update the last EventEntry if it's a duplicate
+        if (isEntrySame(lastEntry, type, event, extras, compoundString, gestureEvent)) {
+            lastEntry.duplicateCount++;
+            return;
+        }
+        EventEntry eventEntry = new EventEntry();
+
+        eventEntry.update(type, event, extras, compoundString, gestureEvent);
+        lastEventEntries.add(eventEntry);
+    }
+
+    public void clear() {
+        Arrays.fill(logs, null);
+    }
+
+    public void dump(String prefix, PrintWriter writer) {
+        writer.println(prefix + "ActiveGestureLog history:");
+        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSSZ  ", Locale.US);
+        Date date = new Date();
+        ArrayList<EventLog> eventLogs = new ArrayList<>();
+
+        for (int i = 0; i < logs.length; i++) {
+            EventLog eventLog = logs[(nextIndex + i) % logs.length];
+            if (eventLog == null) {
+                continue;
+            }
+            eventLogs.add(eventLog);
+            writer.println(prefix + "\tLogs for logId: " + eventLog.logId);
+
+            for (EventEntry eventEntry : eventLog.eventEntries) {
+                date.setTime(eventEntry.time);
+
+                StringBuilder msg = new StringBuilder(prefix + "\t\t").append(sdf.format(date))
+                        .append(eventEntry.event);
+                switch (eventEntry.type) {
+                    case TYPE_BOOL_FALSE:
+                        msg.append(": false");
+                        break;
+                    case TYPE_BOOL_TRUE:
+                        msg.append(": true");
+                        break;
+                    case TYPE_FLOAT:
+                        msg.append(": ").append(eventEntry.extras);
+                        break;
+                    case TYPE_INTEGER:
+                        msg.append(": ").append((int) eventEntry.extras);
+                        break;
+                    case TYPE_INPUT_CONSUMER:
+                        msg.append(eventEntry.mCompoundString);
+                        break;
+                    case TYPE_GESTURE_EVENT:
+                        continue;
+                    default: // fall out
+                }
+                if (eventEntry.duplicateCount > 0) {
+                    msg.append(" & ").append(eventEntry.duplicateCount).append(" similar events");
+                }
+                writer.println(msg);
+            }
+        }
+
+        if (FeatureFlags.ENABLE_GESTURE_ERROR_DETECTION.get()) {
+            ActiveGestureErrorDetector.analyseAndDump(prefix + '\t', writer, eventLogs);
+        }
+    }
+
+    /**
+     * Increments and returns the current log ID. This should be used every time a new log trace
+     * is started.
+     */
+    public int incrementLogId() {
+        return mCurrentLogId++;
+    }
+
+    /** Returns the current log ID. This should be used when a log trace is being reused. */
+    public int getLogId() {
+        return mCurrentLogId;
+    }
+
+    private boolean isEntrySame(
+            EventEntry entry,
+            int type,
+            String event,
+            float extras,
+            CompoundString compoundString,
+            ActiveGestureErrorDetector.GestureEvent gestureEvent) {
+        return entry != null
+                && entry.type == type
+                && entry.event.equals(event)
+                && Float.compare(entry.extras, extras) == 0
+                && entry.mCompoundString.equals(compoundString)
+                && entry.gestureEvent == gestureEvent;
+    }
+
+    /** A single event entry. */
+    protected static class EventEntry {
+
+        private int type;
+        private String event;
+        private float extras;
+        @NonNull private CompoundString mCompoundString;
+        private ActiveGestureErrorDetector.GestureEvent gestureEvent;
+        private long time;
+        private int duplicateCount;
+
+        private EventEntry() {}
+
+        @Nullable
+        protected ActiveGestureErrorDetector.GestureEvent getGestureEvent() {
+            return gestureEvent;
+        }
+
+        private void update(
+                int type,
+                String event,
+                float extras,
+                @NonNull CompoundString compoundString,
+                ActiveGestureErrorDetector.GestureEvent gestureEvent) {
+            this.type = type;
+            this.event = event;
+            this.extras = extras;
+            this.mCompoundString = compoundString;
+            this.gestureEvent = gestureEvent;
+            time = System.currentTimeMillis();
+            duplicateCount = 0;
+        }
+    }
+
+    /** An entire log of entries associated with a single log ID */
+    protected static class EventLog {
+
+        protected final List<EventEntry> eventEntries = new ArrayList<>();
+        protected final int logId;
+
+        private EventLog(int logId) {
+            this.logId = logId;
+        }
+    }
+
+    /** A buildable string stored as an array for memory efficiency. */
+    public static class CompoundString {
+
+        public static final CompoundString NO_OP = new CompoundString();
+
+        private final List<String> mSubstrings;
+
+        private final boolean mIsNoOp;
+
+        private CompoundString() {
+            this(null);
+        }
+
+        public CompoundString(String substring) {
+            mIsNoOp = substring == null;
+            if (mIsNoOp) {
+                mSubstrings = null;
+                return;
+            }
+            mSubstrings = new ArrayList<>();
+            mSubstrings.add(substring);
+        }
+
+        public CompoundString append(CompoundString substring) {
+            if (mIsNoOp) {
+                return this;
+            }
+            mSubstrings.addAll(substring.mSubstrings);
+
+            return this;
+        }
+
+        public CompoundString append(String substring) {
+            if (mIsNoOp) {
+                return this;
+            }
+            mSubstrings.add(substring);
+
+            return this;
+        }
+
+        @Override
+        public String toString() {
+            if (mIsNoOp) {
+                return "ERROR: cannot use No-Op compound string";
+            }
+            StringBuilder sb = new StringBuilder();
+            for (String substring : mSubstrings) {
+                sb.append(substring);
+            }
+
+            return sb.toString();
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mIsNoOp, mSubstrings);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (!(obj instanceof CompoundString)) {
+                return false;
+            }
+            CompoundString other = (CompoundString) obj;
+            return (mIsNoOp == other.mIsNoOp) && Objects.equals(mSubstrings, other.mSubstrings);
+        }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/util/BaseDepthController.java b/quickstep/src/com/android/quickstep/util/BaseDepthController.java
new file mode 100644
index 0000000..29ae9a1
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/BaseDepthController.java
@@ -0,0 +1,167 @@
+/*
+ * 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.app.WallpaperManager;
+import android.os.IBinder;
+import android.util.FloatProperty;
+import android.view.AttachedSurfaceControl;
+import android.view.SurfaceControl;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.util.MultiPropertyFactory;
+import com.android.systemui.shared.system.BlurUtils;
+
+/**
+ * Utility class for applying depth effect
+ */
+public class BaseDepthController {
+
+    private static final FloatProperty<BaseDepthController> DEPTH =
+            new FloatProperty<BaseDepthController>("depth") {
+                @Override
+                public void setValue(BaseDepthController depthController, float depth) {
+                    depthController.setDepth(depth);
+                }
+
+                @Override
+                public Float get(BaseDepthController depthController) {
+                    return depthController.mDepth;
+                }
+            };
+
+    private static final MultiPropertyFactory<BaseDepthController> DEPTH_PROPERTY_FACTORY =
+            new MultiPropertyFactory<>("depthProperty", DEPTH, Float::max);
+
+    private static final int DEPTH_INDEX_STATE_TRANSITION = 1;
+    private static final int DEPTH_INDEX_WIDGET = 2;
+
+    /** Property to set the depth for state transition. */
+    public static final FloatProperty<BaseDepthController> STATE_DEPTH =
+            DEPTH_PROPERTY_FACTORY.get(DEPTH_INDEX_STATE_TRANSITION);
+    /** Property to set the depth for widget picker. */
+    public static final FloatProperty<BaseDepthController> WIDGET_DEPTH =
+            DEPTH_PROPERTY_FACTORY.get(DEPTH_INDEX_WIDGET);
+
+    protected final Launcher mLauncher;
+
+    /**
+     * Blur radius when completely zoomed out, in pixels.
+     */
+    protected final int mMaxBlurRadius;
+    protected final WallpaperManager mWallpaperManager;
+    protected boolean mCrossWindowBlursEnabled;
+
+    /**
+     * Ratio from 0 to 1, where 0 is fully zoomed out, and 1 is zoomed in.
+     * @see android.service.wallpaper.WallpaperService.Engine#onZoomChanged(float)
+     */
+    protected float mDepth;
+
+    protected SurfaceControl mSurface;
+
+    // Hints that there is potentially content behind Launcher and that we shouldn't optimize by
+    // marking the launcher surface as opaque.  Only used in certain Launcher states.
+    private boolean mHasContentBehindLauncher;
+    /**
+     * Last blur value, in pixels, that was applied.
+     * For debugging purposes.
+     */
+    protected int mCurrentBlur;
+    /**
+     * If we requested early wake-up offsets to SurfaceFlinger.
+     */
+    protected boolean mInEarlyWakeUp;
+
+    public BaseDepthController(Launcher activity) {
+        mLauncher = activity;
+        mMaxBlurRadius = activity.getResources().getInteger(R.integer.max_depth_blur_radius);
+        mWallpaperManager = activity.getSystemService(WallpaperManager.class);
+    }
+
+    protected void setCrossWindowBlursEnabled(boolean isEnabled) {
+        mCrossWindowBlursEnabled = isEnabled;
+        applyDepthAndBlur();
+    }
+
+    public void setHasContentBehindLauncher(boolean hasContentBehindLauncher) {
+        mHasContentBehindLauncher = hasContentBehindLauncher;
+    }
+
+    protected void applyDepthAndBlur() {
+        float depth = mDepth;
+        IBinder windowToken = mLauncher.getRootView().getWindowToken();
+        if (windowToken != null) {
+            mWallpaperManager.setWallpaperZoomOut(windowToken, depth);
+        }
+
+        if (!BlurUtils.supportsBlursOnWindows()) {
+            return;
+        }
+        if (mSurface == null || !mSurface.isValid()) {
+            return;
+        }
+        boolean hasOpaqueBg = mLauncher.getScrimView().isFullyOpaque();
+        boolean isSurfaceOpaque = !mHasContentBehindLauncher && hasOpaqueBg;
+
+        mCurrentBlur = !mCrossWindowBlursEnabled || hasOpaqueBg
+                ? 0 : (int) (depth * mMaxBlurRadius);
+        SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()
+                .setBackgroundBlurRadius(mSurface, mCurrentBlur)
+                .setOpaque(mSurface, isSurfaceOpaque);
+
+        // Set early wake-up flags when we know we're executing an expensive operation, this way
+        // SurfaceFlinger will adjust its internal offsets to avoid jank.
+        boolean wantsEarlyWakeUp = depth > 0 && depth < 1;
+        if (wantsEarlyWakeUp && !mInEarlyWakeUp) {
+            transaction.setEarlyWakeupStart();
+            mInEarlyWakeUp = true;
+        } else if (!wantsEarlyWakeUp && mInEarlyWakeUp) {
+            transaction.setEarlyWakeupEnd();
+            mInEarlyWakeUp = false;
+        }
+
+        AttachedSurfaceControl rootSurfaceControl =
+                mLauncher.getRootView().getRootSurfaceControl();
+        if (rootSurfaceControl != null) {
+            rootSurfaceControl.applyTransactionOnDraw(transaction);
+        }
+    }
+
+    protected void setDepth(float depth) {
+        depth = Utilities.boundToRange(depth, 0, 1);
+        // Round out the depth to dedupe frequent, non-perceptable updates
+        int depthI = (int) (depth * 256);
+        float depthF = depthI / 256f;
+        if (Float.compare(mDepth, depthF) == 0) {
+            return;
+        }
+        mDepth = depthF;
+        applyDepthAndBlur();
+    }
+
+    /**
+     * Sets the specified app target surface to apply the blur to.
+     */
+    protected void setSurface(SurfaceControl surface) {
+        if (mSurface != surface) {
+            mSurface = surface;
+            applyDepthAndBlur();
+        }
+    }
+}
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/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
index d0856be..f7136a5 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -23,7 +23,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.DisplayController.NavigationMode;
+import com.android.launcher3.util.NavigationMode;
 import com.android.quickstep.LauncherActivityInterface;
 
 public class LayoutUtils {
diff --git a/quickstep/src/com/android/quickstep/util/LogUtils.kt b/quickstep/src/com/android/quickstep/util/LogUtils.kt
new file mode 100644
index 0000000..bad8506
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/LogUtils.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.util.Pair
+import com.android.internal.logging.InstanceIdSequence
+import com.android.launcher3.logging.InstanceId
+
+object LogUtils {
+  /**
+   * @return a [Pair] of two InstanceIds but with different types, one that can be used by framework
+   * (if needing to pass through an intent or such) and one used in Launcher
+   */
+  @JvmStatic
+  fun getShellShareableInstanceId():
+    Pair<com.android.internal.logging.InstanceId, InstanceId> {
+    val internalInstanceId = InstanceIdSequence(InstanceId.INSTANCE_ID_MAX).newInstanceId()
+    val launcherInstanceId = InstanceId(internalInstanceId.id)
+    return Pair(internalInstanceId, launcherInstanceId)
+  }
+}
diff --git a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
index b83e26e..69ed2f8 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,31 @@
                             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;
+            String logString = "onMotionPauseChanged, paused=" + mIsPaused + " reason=" + reason;
+            if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+                Log.d(TAG, logString);
+            }
+            ActiveGestureLog.INSTANCE.addLog(logString);
             boolean isFirstDetectedPause = !mHasEverBeenPaused && mIsPaused;
             if (mIsPaused) {
                 AccessibilityManagerCompat.sendPauseDetectedEventToTest(mContext);
diff --git a/quickstep/src/com/android/quickstep/util/NavBarPosition.java b/quickstep/src/com/android/quickstep/util/NavBarPosition.java
index 527a6d2..59c8263 100644
--- a/quickstep/src/com/android/quickstep/util/NavBarPosition.java
+++ b/quickstep/src/com/android/quickstep/util/NavBarPosition.java
@@ -15,12 +15,12 @@
  */
 package com.android.quickstep.util;
 
-import static com.android.launcher3.util.DisplayController.NavigationMode.NO_BUTTON;
+import static com.android.launcher3.util.NavigationMode.NO_BUTTON;
 
 import android.view.Surface;
 
 import com.android.launcher3.util.DisplayController.Info;
-import com.android.launcher3.util.DisplayController.NavigationMode;
+import com.android.launcher3.util.NavigationMode;
 
 /**
  * Utility class to check nav bar position.
diff --git a/quickstep/src/com/android/quickstep/util/NormalToSplitTimings.java b/quickstep/src/com/android/quickstep/util/NormalToSplitTimings.java
new file mode 100644
index 0000000..1bf5a5d
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/NormalToSplitTimings.java
@@ -0,0 +1,43 @@
+/*
+ * 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 static com.android.launcher3.anim.Interpolators.LINEAR;
+
+import android.view.animation.Interpolator;
+
+/**
+ * Timings for the Normal > OverviewSplitSelect animation.
+ */
+public class NormalToSplitTimings extends OverviewToSplitTimings implements SplitAnimationTimings {
+    @Override
+    public Interpolator getStagedRectXInterpolator() { return LINEAR; }
+    @Override
+    public Interpolator getStagedRectScaleXInterpolator() { return LINEAR; }
+    @Override
+    public Interpolator getStagedRectScaleYInterpolator() { return LINEAR; }
+
+    public int getScrimFadeInStart() { return 0; }
+    public int getScrimFadeInEnd() { return 167; }
+
+    public float getScrimFadeInStartOffset() {
+        return (float) getScrimFadeInStart() / getDuration();
+    }
+    public float getScrimFadeInEndOffset() {
+        return (float) getScrimFadeInEnd() / getDuration();
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/OverviewToSplitTimings.java b/quickstep/src/com/android/quickstep/util/OverviewToSplitTimings.java
new file mode 100644
index 0000000..f44796b
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/OverviewToSplitTimings.java
@@ -0,0 +1,92 @@
+/*
+ * 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 static com.android.launcher3.anim.Interpolators.DEACCEL_2;
+
+import android.view.animation.Interpolator;
+
+/**
+ * Timings for the Overview > OverviewSplitSelect animation.
+ */
+public class OverviewToSplitTimings implements SplitAnimationTimings {
+    public int getPlaceholderFadeInStart() { return 0; }
+    public int getPlaceholderFadeInEnd() { return 133; }
+    public int getPlaceholderIconFadeInStart() { return 167; }
+    public int getPlaceholderIconFadeInEnd() { return 250; }
+    public int getStagedRectSlideStart() { return 0; }
+    public int getStagedRectSlideEnd() { return 417; }
+    public int getGridSlideStart() { return 67; }
+    public int getGridSlideStagger() { return 16; }
+    public int getGridSlideDuration() { return 500; }
+    public int getActionsFadeStart() { return 0; }
+    public int getActionsFadeEnd() { return 83; }
+    public int getIconFadeStart() { return 0; }
+    public int getIconFadeEnd() { return 83; }
+    public int getInstructionsContainerFadeInStart() { return 167; }
+    public int getInstructionsContainerFadeInEnd() { return 250; }
+    public int getInstructionsTextFadeInStart() { return 217; }
+    public int getInstructionsTextFadeInEnd() { return 300; }
+    public int getInstructionsUnfoldStart() { return 167; }
+    public int getInstructionsUnfoldEnd() { return 500; }
+
+    public int getDuration() { return ENTER_DURATION; }
+    public Interpolator getStagedRectXInterpolator() { return DEACCEL_2; }
+    public Interpolator getStagedRectYInterpolator() { return DEACCEL_2; }
+    public Interpolator getStagedRectScaleXInterpolator() { return DEACCEL_2; }
+    public Interpolator getStagedRectScaleYInterpolator() { return DEACCEL_2; }
+
+    public float getGridSlideStartOffset() {
+        return (float) getGridSlideStart() / getDuration();
+    }
+    public float getGridSlideStaggerOffset() {
+        return (float) getGridSlideStagger() / getDuration();
+    }
+    public float getGridSlideDurationOffset() {
+        return (float) getGridSlideDuration() / getDuration();
+    }
+    public float getActionsFadeStartOffset() {
+        return (float) getActionsFadeStart() / getDuration();
+    }
+    public float getActionsFadeEndOffset() {
+        return (float) getActionsFadeEnd() / getDuration();
+    }
+    public float getIconFadeStartOffset() {
+        return (float) getIconFadeStart() / getDuration();
+    }
+    public float getIconFadeEndOffset() {
+        return (float) getIconFadeEnd() / getDuration();
+    }
+    public float getInstructionsContainerFadeInStartOffset() {
+        return (float) getInstructionsContainerFadeInStart() / getDuration();
+    }
+    public float getInstructionsContainerFadeInEndOffset() {
+        return (float) getInstructionsContainerFadeInEnd() / getDuration();
+    }
+    public float getInstructionsTextFadeInStartOffset() {
+        return (float) getInstructionsTextFadeInStart() / getDuration();
+    }
+    public float getInstructionsTextFadeInEndOffset() {
+        return (float) getInstructionsTextFadeInEnd() / getDuration();
+    }
+    public float getInstructionsUnfoldStartOffset() {
+        return (float) getInstructionsUnfoldStart() / getDuration();
+    }
+    public float getInstructionsUnfoldEndOffset() {
+        return (float) getInstructionsUnfoldEnd() / getDuration();
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/ProxyScreenStatusProvider.java b/quickstep/src/com/android/quickstep/util/ProxyScreenStatusProvider.java
index 3777c65..8f79ccf 100644
--- a/quickstep/src/com/android/quickstep/util/ProxyScreenStatusProvider.java
+++ b/quickstep/src/com/android/quickstep/util/ProxyScreenStatusProvider.java
@@ -39,6 +39,16 @@
         mListeners.forEach(ScreenListener::onScreenTurnedOn);
     }
 
+    /** Called when the screen is starting to turn on. */
+    public void onScreenTurningOn() {
+        mListeners.forEach(ScreenListener::onScreenTurningOn);
+    }
+
+    /** Called when the screen is starting to turn off. */
+    public void onScreenTurningOff() {
+        mListeners.forEach(ScreenListener::onScreenTurningOff);
+    }
+
     @Override
     public void addCallback(@NonNull ScreenListener listener) {
         mListeners.add(listener);
diff --git a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
index fb32581..e928b27 100644
--- a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
+++ b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
@@ -21,7 +21,7 @@
 import static com.android.launcher3.LauncherState.HINT_STATE;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.util.DisplayController.NavigationMode.NO_BUTTON;
+import static com.android.launcher3.util.NavigationMode.NO_BUTTON;
 
 import android.content.SharedPreferences;
 
@@ -29,7 +29,6 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.appprediction.AppsDividerView;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.hybridhotseat.HotseatPredictionController;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.statemanager.StateManager.StateListener;
@@ -88,8 +87,7 @@
             });
         }
 
-        if (DisplayController.getNavigationMode(launcher) == NO_BUTTON
-                && FeatureFlags.ENABLE_ALL_APPS_EDU.get()) {
+        if (DisplayController.getNavigationMode(launcher) == NO_BUTTON) {
             stateManager.addStateListener(new StateListener<LauncherState>() {
                 private static final int MAX_NUM_SWIPES_TO_TRIGGER_EDU = 3;
 
diff --git a/quickstep/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.java b/quickstep/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.java
index edaa326..1d008da 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.java
@@ -15,13 +15,10 @@
  */
 package com.android.quickstep.util;
 
-import static com.android.launcher3.testing.TestProtocol.BAD_STATE;
 import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
 
 import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
-import android.util.Log;
 
 import androidx.dynamicanimation.animation.DynamicAnimation;
 
@@ -30,8 +27,6 @@
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.quickstep.views.RecentsView;
 
-import java.util.Arrays;
-
 public class RecentsAtomicAnimationFactory<ACTIVITY_TYPE extends StatefulActivity, STATE_TYPE>
         extends AtomicAnimationFactory<STATE_TYPE> {
 
@@ -53,27 +48,6 @@
             case INDEX_RECENTS_FADE_ANIM:
                 ObjectAnimator alpha = ObjectAnimator.ofFloat(mActivity.getOverviewPanel(),
                         RecentsView.CONTENT_ALPHA, values);
-                Log.d(BAD_STATE, "RAAF createStateElementAnimation alpha="
-                        + Arrays.toString(values));
-                alpha.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationStart(Animator animation) {
-                        Log.d(BAD_STATE, "RAAF createStateElementAnimation onStart");
-                    }
-
-                    @Override
-                    public void onAnimationCancel(Animator animation) {
-                        RecentsView recent = mActivity.getOverviewPanel();
-                        float alpha = recent == null ? -1 : RecentsView.CONTENT_ALPHA.get(recent);
-                        Log.d(BAD_STATE, "RAAF createStateElementAnimation onCancel, alpha="
-                                + alpha);
-                    }
-
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        Log.d(BAD_STATE, "RAAF createStateElementAnimation onEnd");
-                    }
-                });
                 return alpha;
             case INDEX_RECENTS_TRANSLATE_X_ANIM: {
                 RecentsView rv = mActivity.getOverviewPanel();
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index 6038a22..c459f30 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -46,14 +46,13 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.SettingsCache;
 import com.android.quickstep.BaseActivityInterface;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TaskAnimationManager;
-import com.android.quickstep.views.TaskView;
 
 import java.lang.annotation.Retention;
 import java.util.function.IntConsumer;
@@ -221,8 +220,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;
@@ -400,37 +398,10 @@
      * Returns the scale and pivot so that the provided taskRect can fit the provided full size
      */
     public float getFullScreenScaleAndPivot(Rect taskView, DeviceProfile dp, PointF outPivot) {
-        Rect insets = dp.getInsets();
-        float fullWidth = dp.widthPx;
-        float fullHeight = dp.heightPx;
-        if (TaskView.clipLeft(dp)) {
-            fullWidth -= insets.left;
-        }
-        if (TaskView.clipRight(dp)) {
-            fullWidth -= insets.right;
-        }
-        if (TaskView.clipTop(dp)) {
-            fullHeight -= insets.top;
-        }
-        if (TaskView.clipBottom(dp)) {
-            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) {
-            scale = scale * dp.widthPx / fullWidth;
-        }
-
         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);
+            outPivot.set(taskView.centerX(), taskView.centerY());
         } else {
             float factor = scale / (scale - 1);
             outPivot.set(taskView.left * factor, taskView.top * factor);
diff --git a/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java b/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java
index c4909de..68739ba 100644
--- a/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java
+++ b/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java
@@ -214,7 +214,7 @@
      * @param context The activity context.
      * @param velocityPxPerMs Velocity of swipe in px/ms.
      */
-    public void start(Context context, PointF velocityPxPerMs) {
+    public void start(Context context, @Nullable DeviceProfile profile, PointF velocityPxPerMs) {
         // Only tell caller that we ended if both x and y animations have ended.
         OnAnimationEndListener onXEndListener = ((animation, canceled, centerX, velocityX) -> {
             mRectXAnimEnded = true;
@@ -252,7 +252,13 @@
         float minVisibleChange = Math.abs(1f / mStartRect.height());
         ResourceProvider rp = DynamicResource.provider(context);
         float damping = rp.getFloat(R.dimen.swipe_up_rect_scale_damping_ratio);
-        float stiffness = rp.getFloat(R.dimen.swipe_up_rect_scale_stiffness);
+
+        // Increase the stiffness for devices where we want the window size to transform quicker.
+        boolean shouldUseHigherStiffness = profile != null
+                && (profile.isLandscape || profile.isTablet);
+        float stiffness = shouldUseHigherStiffness
+                ? rp.getFloat(R.dimen.swipe_up_rect_scale_higher_stiffness)
+                : rp.getFloat(R.dimen.swipe_up_rect_scale_stiffness);
 
         mRectScaleAnim = new SpringAnimation(this, RECT_SCALE_PROGRESS)
                 .setSpring(new SpringForce(1f)
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java b/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java
new file mode 100644
index 0000000..133be03
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java
@@ -0,0 +1,87 @@
+/*
+ * 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.view.animation.Interpolator;
+
+/**
+ * An interface that supports the centralization of timing information for splitscreen animations.
+ */
+public interface SplitAnimationTimings {
+    int ENTER_DURATION = 866;
+    int ABORT_DURATION = 500;
+    int CONFIRM_DURATION = 383;
+
+    SplitAnimationTimings OVERVIEW_TO_SPLIT = new OverviewToSplitTimings();
+    SplitAnimationTimings NORMAL_TO_SPLIT = new NormalToSplitTimings();
+    SplitAnimationTimings SPLIT_TO_CONFIRM = new SplitToConfirmTimings();
+
+    // Shared methods
+    int getDuration();
+    int getPlaceholderFadeInStart();
+    int getPlaceholderFadeInEnd();
+    int getPlaceholderIconFadeInStart();
+    int getPlaceholderIconFadeInEnd();
+    int getStagedRectSlideStart();
+    int getStagedRectSlideEnd();
+    Interpolator getStagedRectXInterpolator();
+    Interpolator getStagedRectYInterpolator();
+    Interpolator getStagedRectScaleXInterpolator();
+    Interpolator getStagedRectScaleYInterpolator();
+    default float getPlaceholderFadeInStartOffset() {
+        return (float) getPlaceholderFadeInStart() / getDuration();
+    }
+    default float getPlaceholderFadeInEndOffset() {
+        return (float) getPlaceholderFadeInEnd() / getDuration();
+    }
+    default float getPlaceholderIconFadeInStartOffset() {
+        return (float) getPlaceholderIconFadeInStart() / getDuration();
+    }
+    default float getPlaceholderIconFadeInEndOffset() {
+        return (float) getPlaceholderIconFadeInEnd() / getDuration();
+    }
+    default float getStagedRectSlideStartOffset() {
+        return (float) getStagedRectSlideStart() / getDuration();
+    }
+    default float getStagedRectSlideEndOffset() {
+        return (float) getStagedRectSlideEnd() / getDuration();
+    }
+
+    // Defaults for OverviewToSplit
+    default float getGridSlideStartOffset() { return 0; }
+    default float getGridSlideStaggerOffset() { return 0; }
+    default float getGridSlideDurationOffset() { return 0; }
+    default float getActionsFadeStartOffset() { return 0; }
+    default float getActionsFadeEndOffset() { return 0; }
+    default float getIconFadeStartOffset() { return 0; }
+    default float getIconFadeEndOffset() { return 0; }
+    default float getInstructionsContainerFadeInStartOffset() { return 0; }
+    default float getInstructionsContainerFadeInEndOffset() { return 0; }
+    default float getInstructionsTextFadeInStartOffset() { return 0; }
+    default float getInstructionsTextFadeInEndOffset() { return 0; }
+    default float getInstructionsUnfoldStartOffset() { return 0; }
+    default float getInstructionsUnfoldEndOffset() { return 0; }
+
+    // Defaults for NormalToSplit
+    default float getScrimFadeInStartOffset() { return 0; }
+    default float getScrimFadeInEndOffset() { return 0; }
+
+    // Defaults for SplitToConfirm
+    default float getInstructionsFadeStartOffset() { return 0; }
+    default float getInstructionsFadeEndOffset() { return 0; }
+}
+
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index 2502359..efbe783 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -31,17 +31,28 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ShortcutInfo;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.UserHandle;
 import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
 import android.view.RemoteAnimationAdapter;
 import android.view.SurfaceControl;
 import android.window.TransitionInfo;
 
 import androidx.annotation.Nullable;
 
+import com.android.internal.logging.InstanceId;
+import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statemanager.StateManager;
+import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
 import com.android.quickstep.SystemUiProxy;
@@ -63,26 +74,34 @@
  * and is in the process of either a) selecting a second app or b) exiting intention to invoke split
  */
 public class SplitSelectStateController {
+    private static final String TAG = "SplitSelectStateCtor";
 
     private final Context mContext;
     private final Handler mHandler;
+    private StatsLogManager mStatsLogManager;
     private final SystemUiProxy mSystemUiProxy;
     private final StateManager mStateManager;
     private final DepthController mDepthController;
     private @StagePosition int mStagePosition;
+    private ItemInfo mItemInfo;
     private Intent mInitialTaskIntent;
     private int mInitialTaskId = INVALID_TASK_ID;
     private int mSecondTaskId = INVALID_TASK_ID;
     private String mSecondTaskPackageName;
     private boolean mRecentsAnimationRunning;
+    @Nullable
+    private UserHandle mUser;
     /** If not null, this is the TaskView we want to launch from */
     @Nullable
     private GroupedTaskView mLaunchingTaskView;
+    /** Represents where split is intended to be invoked from. */
+    private StatsLogManager.EventEnum mSplitEvent;
 
     public SplitSelectStateController(Context context, Handler handler, StateManager stateManager,
-            DepthController depthController) {
+            DepthController depthController, StatsLogManager statsLogManager) {
         mContext = context;
         mHandler = handler;
+        mStatsLogManager = statsLogManager;
         mSystemUiProxy = SystemUiProxy.INSTANCE.get(mContext);
         mStateManager = stateManager;
         mDepthController = depthController;
@@ -91,16 +110,25 @@
     /**
      * To be called after first task selected
      */
-    public void setInitialTaskSelect(int taskId, @StagePosition int stagePosition) {
+    public void setInitialTaskSelect(int taskId, @StagePosition int stagePosition,
+            StatsLogManager.EventEnum splitEvent, ItemInfo itemInfo) {
         mInitialTaskId = taskId;
-        mStagePosition = stagePosition;
-        mInitialTaskIntent = null;
+        setInitialData(stagePosition, splitEvent, itemInfo);
     }
 
-    public void setInitialTaskSelect(Intent intent, @StagePosition int stagePosition) {
+    public void setInitialTaskSelect(Intent intent, @StagePosition int stagePosition,
+            @NonNull ItemInfo itemInfo, StatsLogManager.EventEnum splitEvent) {
         mInitialTaskIntent = intent;
+        mUser = itemInfo.user;
+        mItemInfo = itemInfo;
+        setInitialData(stagePosition, splitEvent, itemInfo);
+    }
+
+    private void setInitialData(@StagePosition int stagePosition,
+            StatsLogManager.EventEnum splitEvent, ItemInfo itemInfo) {
+        mItemInfo = itemInfo;
         mStagePosition = stagePosition;
-        mInitialTaskId = INVALID_TASK_ID;
+        mSplitEvent = splitEvent;
     }
 
     /**
@@ -118,11 +146,22 @@
         } else {
             fillInIntent = null;
         }
-        final PendingIntent pendingIntent =
-                mInitialTaskIntent == null ? null : PendingIntent.getActivity(mContext, 0,
-                        mInitialTaskIntent, FLAG_MUTABLE);
+
+        final PendingIntent pendingIntent = mInitialTaskIntent == null ? null : (mUser != null
+                ? PendingIntent.getActivityAsUser(mContext, 0, mInitialTaskIntent,
+                FLAG_MUTABLE, null /* options */, mUser)
+                : PendingIntent.getActivity(mContext, 0, mInitialTaskIntent, FLAG_MUTABLE));
+
+        Pair<InstanceId, com.android.launcher3.logging.InstanceId> instanceIds =
+                LogUtils.getShellShareableInstanceId();
         launchTasks(mInitialTaskId, pendingIntent, fillInIntent, mSecondTaskId, mStagePosition,
-                callback, false /* freezeTaskList */, DEFAULT_SPLIT_RATIO);
+                callback, false /* freezeTaskList */, DEFAULT_SPLIT_RATIO,
+                instanceIds.first);
+
+        mStatsLogManager.logger()
+                .withItemInfo(mItemInfo)
+                .withInstanceId(instanceIds.second)
+                .log(mSplitEvent);
     }
 
 
@@ -158,7 +197,7 @@
     public void launchTasks(int taskId1, int taskId2, @StagePosition int stagePosition,
             Consumer<Boolean> callback, boolean freezeTaskList, float splitRatio) {
         launchTasks(taskId1, null /* taskPendingIntent */, null /* fillInIntent */, taskId2,
-                stagePosition, callback, freezeTaskList, splitRatio);
+                stagePosition, callback, freezeTaskList, splitRatio, null);
     }
 
     /**
@@ -167,10 +206,16 @@
      * fill in intent with a taskId2 are set.
      * @param taskPendingIntent is null when split is initiated from Overview
      * @param stagePosition representing location of task1
+     * @param shellInstanceId loggingId to be used by shell, will be non-null for actions that create
+     *                   a split instance, null for cases that bring existing instaces to the
+     *                   foreground (quickswitch, launching previous pairs from overview)
      */
     public void launchTasks(int taskId1, @Nullable PendingIntent taskPendingIntent,
             @Nullable Intent fillInIntent, int taskId2, @StagePosition int stagePosition,
-            Consumer<Boolean> callback, boolean freezeTaskList, float splitRatio) {
+            Consumer<Boolean> callback, boolean freezeTaskList, float splitRatio,
+            @Nullable InstanceId shellInstanceId) {
+        TestLogging.recordEvent(
+                TestProtocol.SEQUENCE_MAIN, "launchSplitTasks");
         // Assume initial task is for top/left part of screen
         final int[] taskIds = stagePosition == STAGE_POSITION_TOP_OR_LEFT
                 ? new int[]{taskId1, taskId2}
@@ -182,8 +227,9 @@
             mSystemUiProxy.startTasks(taskIds[0], null /* mainOptions */, taskIds[1],
                     null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT, splitRatio,
                     new RemoteTransitionCompat(animationRunner, MAIN_EXECUTOR,
-                            ActivityThread.currentActivityThread().getApplicationThread()));
-            // TODO: handle intent + task with shell transition
+                            ActivityThread.currentActivityThread().getApplicationThread()),
+                    shellInstanceId);
+            // TODO(b/237635859): handle intent/shortcut + task with shell transition
         } else {
             RemoteSplitLaunchAnimationRunner animationRunner =
                     new RemoteSplitLaunchAnimationRunner(taskId1, taskPendingIntent, taskId2,
@@ -200,11 +246,19 @@
             if (taskPendingIntent == null) {
                 mSystemUiProxy.startTasksWithLegacyTransition(taskIds[0], mainOpts.toBundle(),
                         taskIds[1], null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT,
-                        splitRatio, adapter);
+                        splitRatio, adapter, shellInstanceId);
             } else {
-                mSystemUiProxy.startIntentAndTaskWithLegacyTransition(taskPendingIntent,
-                        fillInIntent, taskId2, mainOpts.toBundle(), null /* sideOptions */,
-                        stagePosition, splitRatio, adapter);
+                final ShortcutInfo shortcutInfo = getShortcutInfo(mInitialTaskIntent,
+                        taskPendingIntent.getCreatorUserHandle());
+                if (shortcutInfo != null) {
+                    mSystemUiProxy.startShortcutAndTaskWithLegacyTransition(shortcutInfo, taskId2,
+                            mainOpts.toBundle(), null /* sideOptions */, stagePosition, splitRatio,
+                            adapter, shellInstanceId);
+                } else {
+                    mSystemUiProxy.startIntentAndTaskWithLegacyTransition(taskPendingIntent,
+                            fillInIntent, taskId2, mainOpts.toBundle(), null /* sideOptions */,
+                            stagePosition, splitRatio, adapter, shellInstanceId);
+                }
             }
         }
     }
@@ -213,10 +267,36 @@
         return mStagePosition;
     }
 
+    public StatsLogManager.EventEnum getSplitEvent() {
+        return mSplitEvent;
+    }
+
     public void setRecentsAnimationRunning(boolean running) {
         this.mRecentsAnimationRunning = running;
     }
 
+    @Nullable
+    private ShortcutInfo getShortcutInfo(Intent intent, UserHandle userHandle) {
+        if (intent == null || intent.getPackage() == null) {
+            return null;
+        }
+
+        final String shortcutId = intent.getStringExtra(ShortcutKey.EXTRA_SHORTCUT_ID);
+        if (shortcutId == null) {
+            return null;
+        }
+
+        try {
+            final Context context = mContext.createPackageContextAsUser(
+                    intent.getPackage(), 0 /* flags */, userHandle);
+            return new ShortcutInfo.Builder(context, shortcutId).build();
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.w(TAG, "Failed to create a ShortcutInfo for " + intent.getPackage());
+        }
+
+        return null;
+    }
+
     /**
      * Requires Shell Transitions
      */
@@ -238,8 +318,9 @@
         @Override
         public void startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
                 @NonNull SurfaceControl.Transaction t, @NonNull Runnable finishCallback) {
-            TaskViewUtils.composeRecentsSplitLaunchAnimator(mInitialTaskId,
-                    mInitialTaskPendingIntent, mSecondTaskId, info, t, () -> {
+            TaskViewUtils.composeRecentsSplitLaunchAnimator(mLaunchingTaskView, mStateManager,
+                    mDepthController, mInitialTaskId, mInitialTaskPendingIntent, mSecondTaskId,
+                    info, t, () -> {
                     finishCallback.run();
                     if (mSuccessCallback != null) {
                         mSuccessCallback.accept(true);
@@ -309,6 +390,8 @@
         mStagePosition = SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
         mRecentsAnimationRunning = false;
         mLaunchingTaskView = null;
+        mItemInfo = null;
+        mSplitEvent = null;
     }
 
     /**
@@ -330,4 +413,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/SplitToConfirmTimings.java b/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java
new file mode 100644
index 0000000..84cfb0b
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java
@@ -0,0 +1,48 @@
+/*
+ * 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 static com.android.launcher3.anim.Interpolators.LINEAR;
+
+import android.view.animation.Interpolator;
+
+/**
+ * Timings for the OverviewSplitSelect > confirmed animation.
+ */
+public class SplitToConfirmTimings implements SplitAnimationTimings {
+    public int getPlaceholderFadeInStart() { return 0; }
+    public int getPlaceholderFadeInEnd() { return 133; }
+    public int getPlaceholderIconFadeInStart() { return 167; }
+    public int getPlaceholderIconFadeInEnd() { return 250; }
+    public int getStagedRectSlideStart() { return 0; }
+    public int getStagedRectSlideEnd() { return 383; }
+    public int getInstructionsFadeStart() { return 0; }
+    public int getInstructionsFadeEnd() { return 67; }
+
+    public int getDuration() { return CONFIRM_DURATION; }
+    public Interpolator getStagedRectXInterpolator() { return LINEAR; }
+    public Interpolator getStagedRectYInterpolator() { return LINEAR; }
+    public Interpolator getStagedRectScaleXInterpolator() { return LINEAR; }
+    public Interpolator getStagedRectScaleYInterpolator() { return LINEAR; }
+
+    public float getInstructionsFadeStartOffset() {
+        return (float) getInstructionsFadeStart() / getDuration();
+    }
+    public float getInstructionsFadeEndOffset() {
+        return (float) getInstructionsFadeEnd() / getDuration();
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index 32e08ff..eec8582 100644
--- a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -35,12 +35,12 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
 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;
@@ -48,6 +48,7 @@
 import com.android.launcher3.anim.SpringAnimationBuilder;
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.util.DynamicResource;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.plugins.ResourceProvider;
@@ -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
@@ -278,11 +295,11 @@
     }
 
     private void addDepthAnimationForState(Launcher launcher, LauncherState state, long duration) {
-        if (!(launcher instanceof BaseQuickstepLauncher)) {
+        if (!(launcher instanceof QuickstepLauncher)) {
             return;
         }
         PendingAnimation builder = new PendingAnimation(duration);
-        DepthController depthController = ((BaseQuickstepLauncher) launcher).getDepthController();
+        DepthController depthController = ((QuickstepLauncher) launcher).getDepthController();
         depthController.setStateWithAnimation(state, new StateAnimationConfig(), builder);
         mAnimators.play(builder.buildAnim());
     }
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..1a026fc 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -15,6 +15,8 @@
  */
 package com.android.quickstep.util;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.states.RotationHelper.deltaRotation;
 import static com.android.launcher3.touch.PagedOrientationHandler.MATRIX_POST_TRANSLATE;
@@ -22,9 +24,9 @@
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
 import static com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
+import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
 import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation;
 import static com.android.quickstep.util.RecentsOrientedState.preDisplayRotation;
-import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_FULLSCREEN;
 
 import android.animation.TimeInterpolator;
 import android.content.Context;
@@ -40,7 +42,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,8 +102,8 @@
     // Cached calculations
     private boolean mLayoutValid = false;
     private int mOrientationStateId;
-    private StagedSplitBounds mStagedSplitBounds;
-    private boolean mDrawsBelowRecents;
+    private SplitBounds mSplitBounds;
+    private Boolean mDrawsBelowRecents = null;
     private boolean mIsGridTask;
     private int mTaskRectTranslationX;
     private int mTaskRectTranslationY;
@@ -152,13 +154,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 +182,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 +392,16 @@
                 .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 mDrawsBelowRecents is unset, no reordering will be enforced.
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mDrawsBelowRecents != null) {
+            // In legacy transitions, the animation leashes remain in same hierarchy in the
+            // TaskDisplayArea, so we don't want to bump the layer too high otherwise it will
+            // conflict with layers that WM core positions (ie. the input consumers).  For shell
+            // transitions, the animation leashes are reparented to an animation container so we
+            // can bump layers as needed.
+            builder.withLayer(mDrawsBelowRecents
+                    ? Integer.MIN_VALUE + 1
+                    : ENABLE_SHELL_TRANSITIONS ? Integer.MAX_VALUE : 0);
         }
     }
 
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/util/ViewCapture.java b/quickstep/src/com/android/quickstep/util/ViewCapture.java
new file mode 100644
index 0000000..cfcfce0
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/ViewCapture.java
@@ -0,0 +1,527 @@
+/*
+ * 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 static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.launcher3.util.Executors.createAndStartNewLooper;
+
+import static java.util.stream.Collectors.toList;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.os.Trace;
+import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Base64OutputStream;
+import android.util.Log;
+import android.util.Pair;
+import android.util.SparseArray;
+import android.view.View;
+import android.view.View.OnAttachStateChangeListener;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver.OnDrawListener;
+import android.view.Window;
+
+import androidx.annotation.UiThread;
+import androidx.annotation.WorkerThread;
+
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.util.LooperExecutor;
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.SafeCloseable;
+import com.android.launcher3.view.ViewCaptureData.ExportedData;
+import com.android.launcher3.view.ViewCaptureData.FrameData;
+import com.android.launcher3.view.ViewCaptureData.ViewNode;
+
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Future;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ * Utility class for capturing view data every frame
+ */
+public class ViewCapture {
+
+    private static final String TAG = "ViewCapture";
+
+    // These flags are copies of two private flags in the View class.
+    private static final int PFLAG_INVALIDATED = 0x80000000;
+    private static final int PFLAG_DIRTY_MASK = 0x00200000;
+
+    // Number of frames to keep in memory
+    private static final int MEMORY_SIZE = 2000;
+    // Initial size of the reference pool. This is at least be 5 * total number of views in
+    // Launcher. This allows the first free frames avoid object allocation during view capture.
+    private static final int INIT_POOL_SIZE = 300;
+
+    public static final MainThreadInitializedObject<ViewCapture> INSTANCE =
+            new MainThreadInitializedObject<>(ViewCapture::new);
+
+    private final List<WindowListener> mListeners = new ArrayList<>();
+
+    private final Context mContext;
+    private final LooperExecutor mExecutor;
+
+    // Pool used for capturing view tree on the UI thread.
+    private ViewRef mPool = new ViewRef();
+
+    private ViewCapture(Context context) {
+        mContext = context;
+        if (FeatureFlags.CONTINUOUS_VIEW_TREE_CAPTURE.get()) {
+            Looper looper = createAndStartNewLooper("ViewCapture",
+                    Process.THREAD_PRIORITY_FOREGROUND);
+            mExecutor = new LooperExecutor(looper);
+            mExecutor.execute(this::initPool);
+        } else {
+            mExecutor = UI_HELPER_EXECUTOR;
+        }
+    }
+
+    @UiThread
+    private void addToPool(ViewRef start, ViewRef end) {
+        end.next = mPool;
+        mPool = start;
+    }
+
+    @WorkerThread
+    private void initPool() {
+        ViewRef start = new ViewRef();
+        ViewRef current = start;
+
+        for (int i = 0; i < INIT_POOL_SIZE; i++) {
+            current.next = new ViewRef();
+            current = current.next;
+        }
+
+        ViewRef finalCurrent = current;
+        MAIN_EXECUTOR.execute(() -> addToPool(start, finalCurrent));
+    }
+
+    /**
+     * Attaches the ViewCapture to the provided window and returns a handle to detach the listener
+     */
+    public SafeCloseable startCapture(Window window) {
+        String title = window.getAttributes().getTitle().toString();
+        String name = TextUtils.isEmpty(title) ? window.toString() : title;
+        return startCapture(window.getDecorView(), name);
+    }
+
+    /**
+     * Attaches the ViewCapture to the provided window and returns a handle to detach the listener
+     */
+    public SafeCloseable startCapture(View view, String name) {
+        if (!FeatureFlags.CONTINUOUS_VIEW_TREE_CAPTURE.get()) {
+            return () -> { };
+        }
+
+        WindowListener listener = new WindowListener(view, name);
+        mExecutor.execute(() -> MAIN_EXECUTOR.execute(listener::attachToRoot));
+        mListeners.add(listener);
+        return () -> {
+            mListeners.remove(listener);
+            listener.destroy();
+        };
+    }
+
+    /**
+     * Dumps all the active view captures
+     */
+    public void dump(PrintWriter writer, FileDescriptor out) {
+        if (!FeatureFlags.CONTINUOUS_VIEW_TREE_CAPTURE.get()) {
+            return;
+        }
+        ViewIdProvider idProvider = new ViewIdProvider(mContext.getResources());
+
+        // Collect all the tasks first so that all the tasks are posted on the executor
+        List<Pair<String, Future<ExportedData>>> tasks = mListeners.stream()
+                .map(l -> Pair.create(l.name, mExecutor.submit(() -> l.dumpToProto(idProvider))))
+                .collect(toList());
+
+        tasks.forEach(pair -> {
+            writer.println();
+            writer.println(" ContinuousViewCapture:");
+            writer.println(" window " + pair.first + ":");
+            writer.println("  pkg:" + mContext.getPackageName());
+            writer.print("  data:");
+            writer.flush();
+            try (OutputStream os = new FileOutputStream(out)) {
+                ExportedData data = pair.second.get();
+                OutputStream encodedOS = new GZIPOutputStream(new Base64OutputStream(os,
+                        Base64.NO_CLOSE | Base64.NO_PADDING | Base64.NO_WRAP));
+                data.writeTo(encodedOS);
+                encodedOS.close();
+                os.flush();
+            } catch (Exception e) {
+                Log.e(TAG, "Error capturing proto", e);
+            }
+            writer.println();
+            writer.println("--end--");
+        });
+    }
+
+    private class WindowListener implements OnDrawListener {
+
+        private final View mRoot;
+        public final String name;
+
+        private final Handler mHandler;
+        private final ViewRef mViewRef = new ViewRef();
+
+        private int mFrameIndexBg = -1;
+        private boolean mIsFirstFrame = true;
+        private final long[] mFrameTimesBg = new long[MEMORY_SIZE];
+        private final ViewPropertyRef[] mNodesBg = new ViewPropertyRef[MEMORY_SIZE];
+
+        private boolean mDestroyed = false;
+
+        WindowListener(View view, String name) {
+            mRoot = view;
+            this.name = name;
+            mHandler = new Handler(mExecutor.getLooper(), this::captureViewPropertiesBg);
+        }
+
+        @Override
+        public void onDraw() {
+            Trace.beginSection("view_capture");
+            captureViewTree(mRoot, mViewRef);
+            Message m = Message.obtain(mHandler);
+            m.obj = mViewRef.next;
+            mHandler.sendMessage(m);
+            mIsFirstFrame = false;
+            Trace.endSection();
+        }
+
+        /**
+         * Captures the View property on the background thread, and transfer all the ViewRef objects
+         * back to the pool
+         */
+        @WorkerThread
+        private boolean captureViewPropertiesBg(Message msg) {
+            ViewRef viewRefStart = (ViewRef) msg.obj;
+            long time = msg.getWhen();
+            if (viewRefStart == null) {
+                return false;
+            }
+            mFrameIndexBg++;
+            if (mFrameIndexBg >= MEMORY_SIZE) {
+                mFrameIndexBg = 0;
+            }
+            mFrameTimesBg[mFrameIndexBg] = time;
+
+            ViewPropertyRef recycle = mNodesBg[mFrameIndexBg];
+
+            ViewPropertyRef resultStart = null;
+            ViewPropertyRef resultEnd = null;
+
+            ViewRef viewRefEnd = viewRefStart;
+            while (viewRefEnd != null) {
+                ViewPropertyRef propertyRef = recycle;
+                if (propertyRef == null) {
+                    propertyRef = new ViewPropertyRef();
+                } else {
+                    recycle = recycle.next;
+                    propertyRef.next = null;
+                }
+
+                ViewPropertyRef copy = null;
+                if (viewRefEnd.childCount < 0) {
+                    copy = findInLastFrame(viewRefEnd.view.hashCode());
+                    viewRefEnd.childCount = (copy != null) ? copy.childCount : 0;
+                }
+                viewRefEnd.transferTo(propertyRef);
+
+                if (resultStart == null) {
+                    resultStart = propertyRef;
+                    resultEnd = resultStart;
+                } else {
+                    resultEnd.next = propertyRef;
+                    resultEnd = resultEnd.next;
+                }
+
+                if (copy != null) {
+                    int pending = copy.childCount;
+                    while (pending > 0) {
+                        copy = copy.next;
+                        pending = pending - 1 + copy.childCount;
+
+                        propertyRef = recycle;
+                        if (propertyRef == null) {
+                            propertyRef = new ViewPropertyRef();
+                        } else {
+                            recycle = recycle.next;
+                            propertyRef.next = null;
+                        }
+
+                        copy.transferTo(propertyRef);
+
+                        resultEnd.next = propertyRef;
+                        resultEnd = resultEnd.next;
+                    }
+                }
+
+                if (viewRefEnd.next == null) {
+                    // The compiler will complain about using a non-final variable from
+                    // an outer class in a lambda if we pass in viewRefEnd directly.
+                    final ViewRef finalViewRefEnd = viewRefEnd;
+                    MAIN_EXECUTOR.execute(() -> addToPool(viewRefStart, finalViewRefEnd));
+                    break;
+                }
+                viewRefEnd = viewRefEnd.next;
+            }
+            mNodesBg[mFrameIndexBg] = resultStart;
+            return true;
+        }
+
+        private ViewPropertyRef findInLastFrame(int hashCode) {
+            int lastFrameIndex = (mFrameIndexBg == 0) ? MEMORY_SIZE - 1 : mFrameIndexBg - 1;
+            ViewPropertyRef viewPropertyRef = mNodesBg[lastFrameIndex];
+            while (viewPropertyRef != null && viewPropertyRef.hashCode != hashCode) {
+                viewPropertyRef = viewPropertyRef.next;
+            }
+            return viewPropertyRef;
+        }
+
+        void attachToRoot() {
+            if (mRoot.isAttachedToWindow()) {
+                mRoot.getViewTreeObserver().addOnDrawListener(this);
+            } else {
+                mRoot.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
+                    @Override
+                    public void onViewAttachedToWindow(View v) {
+                        if (!mDestroyed) {
+                            mRoot.getViewTreeObserver().addOnDrawListener(WindowListener.this);
+                        }
+                        mRoot.removeOnAttachStateChangeListener(this);
+                    }
+
+                    @Override
+                    public void onViewDetachedFromWindow(View v) { }
+                });
+            }
+        }
+
+        void destroy() {
+            mRoot.getViewTreeObserver().removeOnDrawListener(this);
+            mDestroyed = true;
+        }
+
+        @WorkerThread
+        private ExportedData dumpToProto(ViewIdProvider idProvider) {
+            ExportedData.Builder dataBuilder = ExportedData.newBuilder();
+            ArrayList<Class> classList = new ArrayList<>();
+
+            int size = (mNodesBg[MEMORY_SIZE - 1] == null) ? mFrameIndexBg + 1 : MEMORY_SIZE;
+            for (int i = size - 1; i >= 0; i--) {
+                int index = (MEMORY_SIZE + mFrameIndexBg - i) % MEMORY_SIZE;
+                ViewNode.Builder nodeBuilder = ViewNode.newBuilder();
+                mNodesBg[index].toProto(idProvider, classList, nodeBuilder);
+                dataBuilder.addFrameData(FrameData.newBuilder()
+                        .setNode(nodeBuilder)
+                        .setTimestamp(mFrameTimesBg[index]));
+            }
+            return dataBuilder
+                    .addAllClassname(classList.stream().map(Class::getName).collect(toList()))
+                    .build();
+        }
+
+        private ViewRef captureViewTree(View view, ViewRef start) {
+            ViewRef ref;
+            if (mPool != null) {
+                ref = mPool;
+                mPool = mPool.next;
+                ref.next = null;
+            } else {
+                ref = new ViewRef();
+            }
+            ref.view = view;
+            start.next = ref;
+            if (view instanceof ViewGroup) {
+                ViewGroup parent = (ViewGroup) view;
+                // If a view has not changed since the last frame, we will copy
+                // its children from the last processed frame's data.
+                if ((view.mPrivateFlags & (PFLAG_INVALIDATED | PFLAG_DIRTY_MASK)) == 0
+                        && !mIsFirstFrame) {
+                    // A negative child count is the signal to copy this view from the last frame.
+                    ref.childCount = -parent.getChildCount();
+                    return ref;
+                }
+                ViewRef result = ref;
+                int childCount = ref.childCount = parent.getChildCount();
+                for (int i = 0; i < childCount; i++) {
+                    result = captureViewTree(parent.getChildAt(i), result);
+                }
+                return result;
+            } else {
+                ref.childCount = 0;
+                return ref;
+            }
+        }
+    }
+
+    private static class ViewPropertyRef {
+        // We store reference in memory to avoid generating and storing too many strings
+        public Class clazz;
+        public int hashCode;
+        public int childCount = 0;
+
+        public int id;
+        public int left, top, right, bottom;
+        public int scrollX, scrollY;
+
+        public float translateX, translateY;
+        public float scaleX, scaleY;
+        public float alpha;
+        public float elevation;
+
+        public int visibility;
+        public boolean willNotDraw;
+        public boolean clipChildren;
+
+        public ViewPropertyRef next;
+
+        public void transferTo(ViewPropertyRef out) {
+            out.clazz = this.clazz;
+            out.hashCode = this.hashCode;
+            out.childCount = this.childCount;
+            out.id = this.id;
+            out.left = this.left;
+            out.top = this.top;
+            out.right = this.right;
+            out.bottom = this.bottom;
+            out.scrollX = this.scrollX;
+            out.scrollY = this.scrollY;
+            out.scaleX = this.scaleX;
+            out.scaleY = this.scaleY;
+            out.translateX = this.translateX;
+            out.translateY = this.translateY;
+            out.alpha = this.alpha;
+            out.visibility = this.visibility;
+            out.willNotDraw = this.willNotDraw;
+            out.clipChildren = this.clipChildren;
+            out.next = this.next;
+            out.elevation = this.elevation;
+        }
+
+        /**
+         * Converts the data to the proto representation and returns the next property ref
+         * at the end of the iteration.
+         * @return
+         */
+        public ViewPropertyRef toProto(ViewIdProvider idProvider, ArrayList<Class> classList,
+                ViewNode.Builder outBuilder) {
+            int classnameIndex = classList.indexOf(clazz);
+            if (classnameIndex < 0) {
+                classnameIndex = classList.size();
+                classList.add(clazz);
+            }
+            outBuilder
+                    .setClassnameIndex(classnameIndex)
+                    .setHashcode(hashCode)
+                    .setId(idProvider.getName(id))
+                    .setLeft(left)
+                    .setTop(top)
+                    .setWidth(right - left)
+                    .setHeight(bottom - top)
+                    .setTranslationX(translateX)
+                    .setTranslationY(translateY)
+                    .setScaleX(scaleX)
+                    .setScaleY(scaleY)
+                    .setAlpha(alpha)
+                    .setVisibility(visibility)
+                    .setWillNotDraw(willNotDraw)
+                    .setElevation(elevation)
+                    .setClipChildren(clipChildren);
+
+            ViewPropertyRef result = next;
+            for (int i = 0; (i < childCount) && (result != null); i++) {
+                ViewNode.Builder childBuilder = ViewNode.newBuilder();
+                result = result.toProto(idProvider, classList, childBuilder);
+                outBuilder.addChildren(childBuilder);
+            }
+            return result;
+        }
+    }
+
+    private static class ViewRef {
+        public View view;
+        public int childCount = 0;
+        public ViewRef next;
+
+        public void transferTo(ViewPropertyRef out) {
+            out.childCount = this.childCount;
+
+            View view = this.view;
+            this.view = null;
+
+            out.clazz = view.getClass();
+            out.hashCode = view.hashCode();
+            out.id = view.getId();
+            out.left = view.getLeft();
+            out.top = view.getTop();
+            out.right = view.getRight();
+            out.bottom = view.getBottom();
+            out.scrollX = view.getScrollX();
+            out.scrollY = view.getScrollY();
+
+            out.translateX = view.getTranslationX();
+            out.translateY = view.getTranslationY();
+            out.scaleX = view.getScaleX();
+            out.scaleY = view.getScaleY();
+            out.alpha = view.getAlpha();
+            out.elevation = view.getElevation();
+
+            out.visibility = view.getVisibility();
+            out.willNotDraw = view.willNotDraw();
+        }
+    }
+
+    private static final class ViewIdProvider {
+
+        private final SparseArray<String> mNames = new SparseArray<>();
+        private final Resources mRes;
+
+        ViewIdProvider(Resources res) {
+            mRes = res;
+        }
+
+        String getName(int id) {
+            String name = mNames.get(id);
+            if (name == null) {
+                if (id >= 0) {
+                    try {
+                        name = mRes.getResourceTypeName(id) + '/' + mRes.getResourceEntryName(id);
+                    } catch (Resources.NotFoundException e) {
+                        name = "id/" + "0x" + Integer.toHexString(id).toUpperCase();
+                    }
+                } else {
+                    name = "NO_ID";
+                }
+                mNames.put(id, name);
+            }
+            return name;
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java b/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java
index 5eb543e..34fa7f1 100644
--- a/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java
+++ b/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java
@@ -32,7 +32,6 @@
 import android.util.FloatProperty;
 import android.view.View;
 
-import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.Hotseat;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
@@ -41,6 +40,7 @@
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.util.DynamicResource;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.plugins.ResourceProvider;
@@ -84,9 +84,9 @@
         }
 
         // Add depth controller animation.
-        if (launcher instanceof BaseQuickstepLauncher) {
+        if (launcher instanceof QuickstepLauncher) {
             PendingAnimation depthBuilder = new PendingAnimation(DURATION_MS);
-            DepthController depth = ((BaseQuickstepLauncher) launcher).getDepthController();
+            DepthController depth = ((QuickstepLauncher) launcher).getDepthController();
             depth.setStateWithAnimation(NORMAL, new StateAnimationConfig(), depthBuilder);
             mAnimators.play(depthBuilder.buildAnim());
         }
diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
index 79b15c7..96504af 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;
@@ -385,4 +385,12 @@
         mBanner.setLayerType(View.LAYER_TYPE_HARDWARE, layerPaint);
         mBanner.setLayerPaint(layerPaint);
     }
+
+    void setBannerVisibility(int visibility) {
+        if (mBanner == null) {
+            return;
+        }
+
+        mBanner.setVisibility(visibility);
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
index c980d1e..1a02f03 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
@@ -1,8 +1,8 @@
 package com.android.quickstep.views;
 
-import static com.android.launcher3.anim.Interpolators.ACCEL;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
+import static com.android.launcher3.AbstractFloatingView.TYPE_TASK_MENU;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.Interpolators.clampToProgress;
 
 import android.animation.ValueAnimator;
 import android.content.Context;
@@ -13,15 +13,16 @@
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
+import android.util.FloatProperty;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
 
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.InsettableFrameLayout;
-import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.PendingAnimation;
@@ -30,6 +31,7 @@
 import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.quickstep.util.MultiValueUpdateListener;
+import com.android.quickstep.util.SplitAnimationTimings;
 import com.android.quickstep.util.TaskCornerRadius;
 import com.android.systemui.shared.system.QuickStepContract;
 
@@ -39,7 +41,8 @@
  * which will have the thumbnail from the provided existing TaskView overlaying the taskview itself.
  *
  * Can then animate the taskview using
- * {@link #addAnimation(PendingAnimation, RectF, Rect, boolean, boolean)}
+ * {@link #addStagingAnimation(PendingAnimation, RectF, Rect, boolean, boolean)} or
+ * {@link #addConfirmAnimation(PendingAnimation, RectF, Rect, boolean, boolean)}
  * giving a starting and ending bounds. Currently this is set to use the split placeholder view,
  * but it could be generified.
  *
@@ -47,6 +50,29 @@
  */
 public class FloatingTaskView extends FrameLayout {
 
+    public static final FloatProperty<FloatingTaskView> PRIMARY_TRANSLATE_OFFSCREEN =
+            new FloatProperty<FloatingTaskView>("floatingTaskPrimaryTranslateOffscreen") {
+        @Override
+        public void setValue(FloatingTaskView view, float translation) {
+            ((RecentsView) view.mActivity.getOverviewPanel()).getPagedOrientationHandler()
+                    .setFloatingTaskPrimaryTranslation(
+                            view,
+                            translation,
+                            view.mActivity.getDeviceProfile()
+                    );
+        }
+
+        @Override
+        public Float get(FloatingTaskView view) {
+            return ((RecentsView) view.mActivity.getOverviewPanel())
+                    .getPagedOrientationHandler()
+                    .getFloatingTaskPrimaryTranslation(
+                            view,
+                            view.mActivity.getDeviceProfile()
+                    );
+        }
+    };
+
     private FloatingTaskThumbnailView mThumbnailView;
     private SplitPlaceholderView mSplitPlaceholderView;
     private RectF mStartingPosition;
@@ -98,7 +124,7 @@
 
         RecentsView recentsView = launcher.getOverviewPanel();
         mOrientationHandler = recentsView.getPagedOrientationHandler();
-        mStagePosition = recentsView.getSplitPlaceholder().getActiveSplitStagePosition();
+        mStagePosition = recentsView.getSplitSelectController().getActiveSplitStagePosition();
         mSplitPlaceholderView.setIcon(icon,
                 mContext.getResources().getDimensionPixelSize(R.dimen.split_placeholder_icon_size));
         mSplitPlaceholderView.getIconView().setRotation(mOrientationHandler.getDegreesRotated());
@@ -110,13 +136,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 +157,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);
@@ -177,8 +209,37 @@
         layout(left, lp.topMargin, left + lp.width, lp.topMargin + lp.height);
     }
 
-    public void addAnimation(PendingAnimation animation, RectF startingBounds, Rect endBounds,
-            boolean fadeWithThumbnail, boolean isStagedTask) {
+    /**
+     * Animates a FloatingTaskThumbnailView and its overlapping SplitPlaceholderView when a split
+     * is staged.
+     */
+    public void addStagingAnimation(PendingAnimation animation, RectF startingBounds,
+            Rect endBounds, boolean fadeWithThumbnail, boolean isStagedTask) {
+        SplitAnimationTimings timings = fadeWithThumbnail
+                ? SplitAnimationTimings.OVERVIEW_TO_SPLIT
+                : SplitAnimationTimings.NORMAL_TO_SPLIT;
+        addAnimation(animation, startingBounds, endBounds, fadeWithThumbnail, isStagedTask,
+                timings);
+    }
+
+    /**
+     * Animates the FloatingTaskThumbnailView and SplitPlaceholderView for the two thumbnails
+     * when a split is confirmed.
+     */
+    public void addConfirmAnimation(PendingAnimation animation, RectF startingBounds,
+            Rect endBounds, boolean fadeWithThumbnail, boolean isStagedTask) {
+        addAnimation(animation, startingBounds, endBounds, fadeWithThumbnail, isStagedTask,
+                SplitAnimationTimings.SPLIT_TO_CONFIRM);
+    }
+
+    /**
+     * Sets up and builds a split staging animation.
+     * Called by {@link #addStagingAnimation(PendingAnimation, RectF, Rect, boolean, boolean)} and
+     * {@link #addConfirmAnimation(PendingAnimation, RectF, Rect, boolean, boolean)}.
+     */
+    public void addAnimation(PendingAnimation animation, RectF startingBounds,
+            Rect endBounds, boolean fadeWithThumbnail, boolean isStagedTask,
+            SplitAnimationTimings timings) {
         mFullscreenParams.setIsStagedTask(isStagedTask);
         final BaseDragLayer dragLayer = mActivity.getDragLayer();
         int[] dragLayerBounds = new int[2];
@@ -192,26 +253,47 @@
         RectF floatingTaskViewBounds = new RectF();
 
         if (fadeWithThumbnail) {
-            animation.addFloat(mSplitPlaceholderView, SplitPlaceholderView.ALPHA_FLOAT,
-                    0, 1, ACCEL);
-            animation.addFloat(mThumbnailView, LauncherAnimUtils.VIEW_ALPHA,
-                    1, 0, DEACCEL_3);
+            // This code block runs for the placeholder view during Overview > OverviewSplitSelect
+            // and for the selected (secondary) thumbnail during OverviewSplitSelect > Confirmed
+
+            // FloatingTaskThumbnailView: thumbnail fades out to transparent
+            animation.setViewAlpha(mThumbnailView, 0, clampToProgress(LINEAR,
+                    timings.getPlaceholderFadeInStartOffset(),
+                    timings.getPlaceholderFadeInEndOffset()));
+
+            // SplitPlaceholderView: gray background fades in at same time, then new icon fades in
+            fadeInSplitPlaceholder(animation, timings);
         } else if (isStagedTask) {
-            // Fade in the placeholder view when split is initiated from homescreen / all apps
-            // icons.
+            // This code block runs for the placeholder view during Normal > OverviewSplitSelect
+            // and for the placeholder (primary) thumbnail during OverviewSplitSelect > Confirmed
+
+            // Fade in the placeholder view during Normal > OverviewSplitSelect
             if (mSplitPlaceholderView.getAlpha() == 0) {
-                animation.addFloat(mSplitPlaceholderView, SplitPlaceholderView.ALPHA_FLOAT,
-                        0.3f, 1, ACCEL);
+                mSplitPlaceholderView.getIconView().setAlpha(0);
+                fadeInSplitPlaceholder(animation, timings);
             }
+
+            // No-op for placeholder during OverviewSplitSelect > Confirmed, alpha should be set
         }
 
         MultiValueUpdateListener listener = new MultiValueUpdateListener() {
-            final FloatProp mDx = new FloatProp(0, prop.dX, 0, animDuration, LINEAR);
-            final FloatProp mDy = new FloatProp(0, prop.dY, 0, animDuration, LINEAR);
+            // SplitPlaceholderView: rectangle translates and stretches to new position
+            final FloatProp mDx = new FloatProp(0, prop.dX, 0, animDuration,
+                    clampToProgress(timings.getStagedRectXInterpolator(),
+                            timings.getStagedRectSlideStartOffset(),
+                            timings.getStagedRectSlideEndOffset()));
+            final FloatProp mDy = new FloatProp(0, prop.dY, 0, animDuration,
+                    clampToProgress(timings.getStagedRectYInterpolator(),
+                            timings.getStagedRectSlideStartOffset(),
+                            timings.getStagedRectSlideEndOffset()));
             final FloatProp mTaskViewScaleX = new FloatProp(1f, prop.finalTaskViewScaleX, 0,
-                    animDuration, LINEAR);
+                    animDuration, clampToProgress(timings.getStagedRectScaleXInterpolator(),
+                    timings.getStagedRectSlideStartOffset(),
+                    timings.getStagedRectSlideEndOffset()));
             final FloatProp mTaskViewScaleY = new FloatProp(1f, prop.finalTaskViewScaleY, 0,
-                    animDuration, LINEAR);
+                    animDuration, clampToProgress(timings.getStagedRectScaleYInterpolator(),
+                    timings.getStagedRectSlideStartOffset(),
+                    timings.getStagedRectSlideEndOffset()));
             @Override
             public void onUpdate(float percent, boolean initOnly) {
                 // Calculate the icon position.
@@ -223,9 +305,19 @@
                 update(floatingTaskViewBounds, percent);
             }
         };
+
         transitionAnimator.addUpdateListener(listener);
     }
 
+    void fadeInSplitPlaceholder(PendingAnimation animation, SplitAnimationTimings timings) {
+        animation.setViewAlpha(mSplitPlaceholderView, 1, clampToProgress(LINEAR,
+                timings.getPlaceholderFadeInStartOffset(),
+                timings.getPlaceholderFadeInEndOffset()));
+        animation.setViewAlpha(mSplitPlaceholderView.getIconView(), 1, clampToProgress(LINEAR,
+                timings.getPlaceholderIconFadeInStartOffset(),
+                timings.getPlaceholderIconFadeInEndOffset()));
+    }
+
     void drawRoundedRect(Canvas canvas, Paint paint) {
         if (mFullscreenParams == null) {
             return;
@@ -246,12 +338,16 @@
      *                        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);
     }
 
+    public int getStagePosition() {
+        return mStagePosition;
+    }
+
     private static class SplitOverlayProperties {
 
         private final float finalTaskViewScaleX;
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index 244a794..3a5f606 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -1,5 +1,6 @@
 package com.android.quickstep.views;
 
+import static com.android.launcher3.AbstractFloatingView.getAnyView;
 import static com.android.launcher3.util.SplitConfigurationOptions.DEFAULT_SPLIT_RATIO;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
@@ -13,11 +14,12 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.DeviceProfile;
 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 +55,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 +80,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 +128,8 @@
         }
     }
 
-    public void updateSplitBoundsConfig(StagedSplitBounds stagedSplitBounds) {
-        mSplitBoundsConfig = stagedSplitBounds;
+    public void updateSplitBoundsConfig(SplitBounds splitBounds) {
+        mSplitBoundsConfig = splitBounds;
         invalidate();
     }
 
@@ -162,6 +164,21 @@
         }
     }
 
+    @Override
+    protected boolean showTaskMenuWithContainer(IconView iconView) {
+        boolean showedTaskMenu = super.showTaskMenuWithContainer(iconView);
+        if (iconView == mIconView2 && showedTaskMenu && !mActivity.getDeviceProfile().isTablet) {
+            // Adjust the position of the secondary task's menu view (only on phones)
+            TaskMenuView taskMenuView = getAnyView(mActivity, AbstractFloatingView.TYPE_TASK_MENU);
+            DeviceProfile deviceProfile = mActivity.getDeviceProfile();
+            getRecentsView().getPagedOrientationHandler()
+                    .setSecondaryTaskMenuPosition(mSplitBoundsConfig, this,
+                            deviceProfile, mTaskIdAttributeContainer[0].getThumbnailView(),
+                            taskMenuView);
+        }
+        return showedTaskMenu;
+    }
+
     @Nullable
     @Override
     public RunnableList launchTaskAnimated() {
@@ -174,7 +191,7 @@
         // Callbacks run from remote animation when recents animation not currently running
         InteractionJankMonitorWrapper.begin(this,
                 InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER, "Enter form GroupedTaskView");
-        recentsView.getSplitPlaceholder().launchTasks(this /*groupedTaskView*/,
+        recentsView.getSplitSelectController().launchTasks(this /*groupedTaskView*/,
                 success -> {
                     endCallback.executeAllAndDestroy();
                     InteractionJankMonitorWrapper.end(
@@ -189,7 +206,7 @@
 
     @Override
     public void launchTask(@NonNull Consumer<Boolean> callback, boolean freezeTaskList) {
-        getRecentsView().getSplitPlaceholder().launchTasks(mTask.key.id, mSecondaryTask.key.id,
+        getRecentsView().getSplitSelectController().launchTasks(mTask.key.id, mSecondaryTask.key.id,
                 STAGE_POSITION_TOP_OR_LEFT, callback, freezeTaskList, getSplitRatio());
     }
 
@@ -213,11 +230,12 @@
     }
 
     @Override
-    protected int getChildTaskIndexAtPosition(PointF position) {
-        if (isCoordInView(mIconView2, position) || isCoordInView(mSnapshotView2, position)) {
+    protected int getLastSelectedChildTaskIndex() {
+        if (isCoordInView(mIconView2, mLastTouchDownPosition)
+                || isCoordInView(mSnapshotView2, mLastTouchDownPosition)) {
             return 1;
         }
-        return super.getChildTaskIndexAtPosition(position);
+        return super.getLastSelectedChildTaskIndex();
     }
 
     private boolean isCoordInView(View v, PointF position) {
@@ -250,8 +268,7 @@
 
     @Override
     public void setOverlayEnabled(boolean overlayEnabled) {
-        super.setOverlayEnabled(overlayEnabled);
-        mSnapshotView2.setOverlayEnabled(overlayEnabled);
+        // Intentional no-op to prevent setting smart actions overlay on thumbnails
     }
 
     @Override
@@ -313,4 +330,33 @@
         mSnapshotView2.setDimAlpha(amount);
         mDigitalWellBeingToast2.setBannerColorTint(tintColor, amount);
     }
+
+    @Override
+    protected void applyThumbnailSplashAlpha() {
+        super.applyThumbnailSplashAlpha();
+        mSnapshotView2.setSplashAlpha(mTaskThumbnailSplashAlpha);
+    }
+
+    /**
+     *     Sets visibility for thumbnails and associated elements (DWB banners).
+     *     IconView is unaffected.
+     *
+     *     When setting INVISIBLE, sets the visibility for the last selected child task.
+     *     When setting VISIBLE (as a reset), sets the visibility for both tasks.
+     */
+    @Override
+    void setThumbnailVisibility(int visibility) {
+        if (visibility == VISIBLE) {
+            mSnapshotView.setVisibility(visibility);
+            mDigitalWellBeingToast.setBannerVisibility(visibility);
+            mSnapshotView2.setVisibility(visibility);
+            mDigitalWellBeingToast2.setBannerVisibility(visibility);
+        } else if (getLastSelectedChildTaskIndex() == 0) {
+            mSnapshotView.setVisibility(visibility);
+            mDigitalWellBeingToast.setBannerVisibility(visibility);
+        } else {
+            mSnapshotView2.setVisibility(visibility);
+            mDigitalWellBeingToast2.setBannerVisibility(visibility);
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 306ebd7..bb8506d 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -21,24 +21,24 @@
 import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
 import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
 import static com.android.launcher3.LauncherState.SPRING_LOADED;
-import static com.android.launcher3.testing.TestProtocol.BAD_STATE;
 
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.os.Build;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.MotionEvent;
 import android.view.Surface;
 
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.popup.QuickstepSystemShortcut;
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statemanager.StateManager.StateListener;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
+import com.android.launcher3.util.PendingSplitSelectInfo;
 import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.quickstep.LauncherActivityInterface;
 import com.android.quickstep.util.SplitSelectStateController;
@@ -47,7 +47,7 @@
  * {@link RecentsView} used in Launcher activity
  */
 @TargetApi(Build.VERSION_CODES.O)
-public class LauncherRecentsView extends RecentsView<BaseQuickstepLauncher, LauncherState>
+public class LauncherRecentsView extends RecentsView<QuickstepLauncher, LauncherState>
         implements StateListener<LauncherState> {
 
     public LauncherRecentsView(Context context) {
@@ -67,7 +67,6 @@
     public void init(OverviewActionsView actionsView,
             SplitSelectStateController splitPlaceholderView) {
         super.init(actionsView, splitPlaceholderView);
-        Log.d(BAD_STATE, "LauncherRecentsView init setContentAlpha=0");
         setContentAlpha(0);
     }
 
@@ -89,9 +88,23 @@
     }
 
     @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(), recoveryData.getSource()
+                );
+                mActivity.finishSplitSelectRecovery();
+            }
+        }
+    }
+
+    @Override
     public void reset() {
         super.reset();
-
         setLayoutRotation(Surface.ROTATION_0, Surface.ROTATION_0);
     }
 
@@ -103,7 +116,6 @@
         if (toState == OVERVIEW_MODAL_TASK) {
             setOverviewSelectEnabled(true);
         }
-        Log.d(BAD_STATE, "LRV onStateTransitionStart setFreezeVisibility=true, toState=" + toState);
         setFreezeViewVisibility(true);
     }
 
@@ -115,8 +127,6 @@
         }
         boolean isOverlayEnabled = finalState == OVERVIEW || finalState == OVERVIEW_MODAL_TASK;
         setOverlayEnabled(isOverlayEnabled);
-        Log.d(BAD_STATE, "LRV onStateTransitionComplete setFreezeVisibility=false, finalState="
-                + finalState);
         setFreezeViewVisibility(false);
         if (finalState != OVERVIEW_MODAL_TASK) {
             setOverviewSelectEnabled(false);
@@ -177,8 +187,9 @@
 
     @Override
     public void initiateSplitSelect(TaskView taskView,
-            @SplitConfigurationOptions.StagePosition int stagePosition) {
-        super.initiateSplitSelect(taskView, stagePosition);
+            @SplitConfigurationOptions.StagePosition int stagePosition,
+            StatsLogManager.EventEnum splitEvent) {
+        super.initiateSplitSelect(taskView, stagePosition, splitEvent);
         mActivity.getStateManager().goToState(LauncherState.OVERVIEW_SPLIT_SELECT);
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index 1c4e497..08a17c4 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;
@@ -33,11 +31,10 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.R;
-import com.android.launcher3.uioverrides.ApiWrapper;
 import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.DisplayController.NavigationMode;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
+import com.android.launcher3.util.NavigationMode;
 import com.android.quickstep.TaskOverlayFactory.OverlayUICallbacks;
 import com.android.quickstep.util.LayoutUtils;
 
@@ -56,7 +53,9 @@
             HIDDEN_NON_ZERO_ROTATION,
             HIDDEN_NO_TASKS,
             HIDDEN_NO_RECENTS,
-            HIDDEN_SPLIT_SCREEN})
+            HIDDEN_SPLIT_SCREEN,
+            HIDDEN_SPLIT_SELECT_ACTIVE
+    })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ActionsHiddenFlags { }
 
@@ -64,6 +63,7 @@
     public static final int HIDDEN_NO_TASKS = 1 << 1;
     public static final int HIDDEN_NO_RECENTS = 1 << 2;
     public static final int HIDDEN_SPLIT_SCREEN = 1 << 3;
+    public static final int HIDDEN_SPLIT_SELECT_ACTIVE = 1 << 4;
 
     @IntDef(flag = true, value = {
             DISABLED_SCROLLING,
@@ -80,8 +80,14 @@
     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;
+    public @interface SplitButtonDisabledFlags { }
+
+    public static final int FLAG_IS_NOT_TABLET = 1 << 0;
+    public static final int FLAG_SINGLE_TASK = 1 << 1;
+
+    private MultiValueAlpha mMultiValueAlpha;
     private Button mSplitButton;
 
     @ActionsHiddenFlags
@@ -90,6 +96,9 @@
     @ActionsDisabledFlags
     protected int mDisabledFlags;
 
+    @SplitButtonDisabledFlags
+    private int mSplitButtonDisabledFlags;
+
     @Nullable
     protected T mCallbacks;
 
@@ -107,13 +116,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);
@@ -183,6 +193,20 @@
         LayoutUtils.setViewEnabled(this, isEnabled);
     }
 
+    /**
+     * Updates the proper flags to indicate whether the "Split screen" button should be enabled.
+     *
+     * @param flag          The flag to update.
+     * @param enable        Whether to enable the disable flag: True will cause view to be disabled.
+     */
+    public void updateSplitButtonFlags(@SplitButtonDisabledFlags int flag, boolean enable) {
+        if (enable) {
+            mSplitButtonDisabledFlags |= flag;
+        } else {
+            mSplitButtonDisabledFlags &= ~flag;
+        }
+    }
+
     public AlphaProperty getContentAlpha() {
         return mMultiValueAlpha.getProperty(INDEX_CONTENT_ALPHA);
     }
@@ -195,6 +219,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.
      */
@@ -202,10 +230,13 @@
         if (mDp == null) {
             return;
         }
-        boolean alignFor3ButtonTaskbar = mDp.isTaskbarPresent && !mDp.isGestureMode;
-        if (alignFor3ButtonTaskbar) {
+        boolean largeScreenLandscape = mDp.isTablet && !mDp.isTwoPanels && mDp.isLandscape;
+        // If in 3-button mode, shift action buttons to accommodate 3-button layout.
+        // (Special exception for landscape tablets, where there is enough room and we don't need to
+        // shift the action buttons.)
+        if (mDp.areNavButtonsInline && !largeScreenLandscape) {
             // Add extra horizontal spacing
-            int additionalPadding = ApiWrapper.getHotseatEndOffset(getContext());
+            int additionalPadding = mDp.hotseatBarEndOffset;
             if (isLayoutRtl()) {
                 setPadding(mInsets.left + additionalPadding, 0, mInsets.right, 0);
             } else {
@@ -233,10 +264,6 @@
             return 0;
         }
 
-        if (mDp.isVerticalBarLayout()) {
-            return mDp.getInsets().bottom;
-        }
-
         if (!mDp.isGestureMode && mDp.isTaskbarPresent) {
             return mDp.getOverviewActionsClaimedSpaceBelow();
         }
@@ -254,12 +281,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(
@@ -267,12 +288,17 @@
                 0, 0, 0);
     }
 
-    public void setSplitButtonVisible(boolean visible) {
+    /**
+     * Shows/hides the "Split" button based on the status of mHiddenFlags.
+     */
+    public void updateSplitButtonVisibility() {
         if (mSplitButton == null) {
             return;
         }
-
-        mSplitButton.setVisibility(visible ? VISIBLE : GONE);
-        findViewById(R.id.action_split_space).setVisibility(visible ? VISIBLE : GONE);
+        boolean shouldBeVisible = mSplitButtonDisabledFlags == 0
+                // and neither of these flags are active
+                && (mHiddenFlags & (HIDDEN_SPLIT_SCREEN | HIDDEN_SPLIT_SELECT_ACTIVE)) == 0;
+        mSplitButton.setVisibility(shouldBeVisible ? VISIBLE : GONE);
+        findViewById(R.id.action_split_space).setVisibility(shouldBeVisible ? VISIBLE : GONE);
     }
 }
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 2360396..96607a0 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -34,15 +34,19 @@
 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.EMPHASIZED_DECELERATE;
 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;
+import static com.android.launcher3.anim.Interpolators.OVERSHOOT_0_75;
 import static com.android.launcher3.anim.Interpolators.clampToProgress;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_ACTIONS_SPLIT;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_CLEAR_ALL;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_DISMISS_SWIPE_UP;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_SWIPE_DOWN;
-import static com.android.launcher3.statehandlers.DepthController.DEPTH;
+import static com.android.launcher3.statehandlers.DepthController.STATE_DEPTH;
 import static com.android.launcher3.touch.PagedOrientationHandler.CANVAS_TRANSLATE;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
@@ -51,10 +55,13 @@
 import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK;
 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
 import static com.android.quickstep.views.ClearAllButton.DISMISS_ALPHA;
+import static com.android.quickstep.views.OverviewActionsView.FLAG_IS_NOT_TABLET;
+import static com.android.quickstep.views.OverviewActionsView.FLAG_SINGLE_TASK;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NON_ZERO_ROTATION;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_RECENTS;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_TASKS;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_SPLIT_SCREEN;
+import static com.android.quickstep.views.OverviewActionsView.HIDDEN_SPLIT_SELECT_ACTIVE;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -128,6 +135,7 @@
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.cache.HandlerRunnable;
+import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.popup.QuickstepSystemShortcut;
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statemanager.BaseState;
@@ -140,8 +148,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 +159,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;
@@ -162,13 +169,17 @@
 import com.android.quickstep.TaskViewUtils;
 import com.android.quickstep.TopTaskTracker;
 import com.android.quickstep.ViewUtils;
+import com.android.quickstep.util.ActiveGestureErrorDetector;
+import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.GroupTask;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.util.RecentsOrientedState;
+import com.android.quickstep.util.SplitAnimationTimings;
 import com.android.quickstep.util.SplitScreenBounds;
 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;
@@ -362,6 +373,10 @@
                 }
             };
 
+    /**
+     * Progress of Recents view from carousel layout to grid layout. If Recents is not shown as a
+     * grid, then the value remains 0.
+     */
     public static final FloatProperty<RecentsView> RECENTS_GRID_PROGRESS =
             new FloatProperty<RecentsView>("recentsGrid") {
                 @Override
@@ -375,6 +390,23 @@
                 }
             };
 
+    /**
+     * Alpha of the task thumbnail splash, where being in BackgroundAppState has a value of 1, and
+     * being in any other state has a value of 0.
+     */
+    public static final FloatProperty<RecentsView> TASK_THUMBNAIL_SPLASH_ALPHA =
+            new FloatProperty<RecentsView>("taskThumbnailSplashAlpha") {
+                @Override
+                public void setValue(RecentsView view, float taskThumbnailSplashAlpha) {
+                    view.setTaskThumbnailSplashAlpha(taskThumbnailSplashAlpha);
+                }
+
+                @Override
+                public Float get(RecentsView view) {
+                    return view.mTaskThumbnailSplashAlpha;
+                }
+            };
+
     // OverScroll constants
     private static final int OVERSCROLL_PAGE_SNAP_ANIMATION_DURATION = 270;
 
@@ -465,6 +497,7 @@
     protected float mTaskViewsSecondarySplitTranslation = 0;
     // Progress from 0 to 1 where 0 is a carousel and 1 is a 2 row grid.
     private float mGridProgress = 0;
+    private float mTaskThumbnailSplashAlpha = 0;
     private boolean mShowAsGridLastOnLayout = false;
     private final IntSet mTopRowIdSet = new IntSet();
 
@@ -622,14 +655,16 @@
     @Nullable
     private TaskView mSplitHiddenTaskView;
     @Nullable
-    private View mSecondSplitHiddenView;
+    private TaskView 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;
 
@@ -877,7 +912,7 @@
         mSplitSelectStateController = splitController;
     }
 
-    public SplitSelectStateController getSplitPlaceholder() {
+    public SplitSelectStateController getSplitSelectController() {
         return mSplitSelectStateController;
     }
 
@@ -1208,6 +1243,8 @@
     @Override
     protected void onPageEndTransition() {
         super.onPageEndTransition();
+        ActiveGestureLog.INSTANCE.addLog(
+                "onPageEndTransition: current page index updated", getNextPage());
         if (isClearAllHidden() && !mActivity.getDeviceProfile().isTablet) {
             mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, false);
         }
@@ -1398,6 +1435,12 @@
         updateGridProperties();
     }
 
+    @Override
+    protected void onScrollerAnimationAborted() {
+        ActiveGestureLog.INSTANCE.addLog("scroller animation aborted",
+                ActiveGestureErrorDetector.GestureEvent.SCROLLER_ANIMATION_ABORTED);
+    }
+
     protected void applyLoadPlan(ArrayList<GroupTask> taskGroups) {
         if (mPendingAnimation != null) {
             mPendingAnimation.addEndListener(success -> applyLoadPlan(taskGroups));
@@ -1445,11 +1488,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);
             }
@@ -1582,6 +1625,7 @@
                 taskView.setStableAlpha(mContentAlpha);
                 taskView.setFullscreenProgress(mFullscreenProgress);
                 taskView.setModalness(mTaskModalness);
+                taskView.setTaskThumbnailSplashAlpha(mTaskThumbnailSplashAlpha);
             }
         }
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
@@ -2180,12 +2224,13 @@
             @Nullable AnimatorSet animatorSet, GestureState.GestureEndTarget endTarget,
             TaskViewSimulator[] taskViewSimulators) {
         mCurrentGestureEndTarget = endTarget;
-        if (endTarget == GestureState.GestureEndTarget.RECENTS) {
+        boolean isOverviewEndTarget = endTarget == GestureState.GestureEndTarget.RECENTS;
+        if (isOverviewEndTarget) {
             updateGridProperties();
         }
 
-        if (mSizeStrategy.stateFromGestureEndTarget(endTarget)
-                .displayOverviewTasksAsGrid(mActivity.getDeviceProfile())) {
+        BaseState<?> endState = mSizeStrategy.stateFromGestureEndTarget(endTarget);
+        if (endState.displayOverviewTasksAsGrid(mActivity.getDeviceProfile())) {
             TaskView runningTaskView = getRunningTaskView();
             float runningTaskPrimaryGridTranslation = 0;
             if (runningTaskView != null) {
@@ -2209,6 +2254,13 @@
                 }
             }
         }
+        int splashAlpha = endState.showTaskThumbnailSplash() ? 1 : 0;
+        if (animatorSet == null) {
+            setTaskThumbnailSplashAlpha(splashAlpha);
+        } else {
+            animatorSet.play(
+                    ObjectAnimator.ofFloat(this, TASK_THUMBNAIL_SPLASH_ALPHA, splashAlpha));
+        }
     }
 
     /**
@@ -2300,7 +2352,7 @@
         boolean runningTaskTileHidden = mRunningTaskTileHidden;
         setCurrentTask(runningTaskViewId);
         mFocusedTaskViewId = runningTaskViewId;
-        setCurrentPage(getRunningTaskIndex());
+        runOnPageScrollsInitialized(() -> setCurrentPage(getRunningTaskIndex()));
         setRunningTaskViewShowScreenshot(false);
         setRunningTaskHidden(runningTaskTileHidden);
         // Update task size after setting current task.
@@ -2658,6 +2710,18 @@
         mClearAllButton.setGridProgress(gridProgress);
     }
 
+    private void setTaskThumbnailSplashAlpha(float taskThumbnailSplashAlpha) {
+        int taskCount = getTaskViewCount();
+        if (taskCount == 0) {
+            return;
+        }
+
+        mTaskThumbnailSplashAlpha = taskThumbnailSplashAlpha;
+        for (int i = 0; i < taskCount; i++) {
+            requireTaskViewAt(i).setTaskThumbnailSplashAlpha(taskThumbnailSplashAlpha);
+        }
+    }
+
     private void enableLayoutTransitions() {
         if (mLayoutTransition == null) {
             mLayoutTransition = new LayoutTransition();
@@ -2745,33 +2809,57 @@
         mOrientationHandler.getInitialSplitPlaceholderBounds(mSplitPlaceholderSize,
                 mSplitPlaceholderInset, mActivity.getDeviceProfile(),
                 mSplitSelectStateController.getActiveSplitStagePosition(), mTempRect);
+        SplitAnimationTimings timings = SplitAnimationTimings.OVERVIEW_TO_SPLIT;
 
         RectF startingTaskRect = new RectF();
+        safeRemoveDragLayerView(mFirstFloatingTaskView);
         if (mSplitHiddenTaskView != null) {
-            mSplitHiddenTaskView.setVisibility(INVISIBLE);
+            // Create the split select animation from Overview
+            mSplitHiddenTaskView.setThumbnailVisibility(INVISIBLE);
+            anim.setViewAlpha(mSplitHiddenTaskView.getIconView(), 0, clampToProgress(LINEAR,
+                    timings.getIconFadeStartOffset(),
+                    timings.getIconFadeEndOffset()));
             mFirstFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
                     mSplitHiddenTaskView.getThumbnail(),
                     mSplitHiddenTaskView.getThumbnail().getThumbnail(),
                     mSplitHiddenTaskView.getIconView().getDrawable(), startingTaskRect);
             mFirstFloatingTaskView.setAlpha(1);
-            mFirstFloatingTaskView.addAnimation(anim, startingTaskRect, mTempRect,
+            mFirstFloatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect,
                     true /* fadeWithThumbnail */, true /* isStagedTask */);
         } else {
+            // Create the split select animation from Home
             mFirstFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
                     mSplitSelectSource.view, null /* thumbnail */,
                     mSplitSelectSource.drawable, startingTaskRect);
             mFirstFloatingTaskView.setAlpha(1);
-            mFirstFloatingTaskView.addAnimation(anim, startingTaskRect, mTempRect,
+            mFirstFloatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect,
                     false /* fadeWithThumbnail */, true /* isStagedTask */);
         }
+
+        // SplitInstructionsView: animate in
+        safeRemoveDragLayerView(mSplitInstructionsView);
+        mSplitInstructionsView = SplitInstructionsView.getSplitInstructionsView(mActivity);
+        mSplitInstructionsView.setAlpha(0);
+        anim.setViewAlpha(mSplitInstructionsView, 1, clampToProgress(LINEAR,
+                timings.getInstructionsContainerFadeInStartOffset(),
+                timings.getInstructionsContainerFadeInEndOffset()));
+        anim.setViewAlpha(mSplitInstructionsView.getTextView(), 1, clampToProgress(LINEAR,
+                timings.getInstructionsTextFadeInStartOffset(),
+                timings.getInstructionsTextFadeInEndOffset()));
+        anim.addFloat(mSplitInstructionsView, mSplitInstructionsView.UNFOLD, 0.1f, 1,
+                clampToProgress(EMPHASIZED_DECELERATE,
+                        timings.getInstructionsUnfoldStartOffset(),
+                        timings.getInstructionsUnfoldEndOffset()));
+
         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 {
+                // If transition to split select was interrupted, clean up to prevent glitches
+                resetFromSplitSelectionState();
                 InteractionJankMonitorWrapper.cancel(
                         InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
             }
@@ -2788,17 +2876,16 @@
      * @param dismissingForSplitSelection task dismiss animation is used for entering split
      *                                    selection state from app icon
      */
-    public PendingAnimation createTaskDismissAnimation(TaskView dismissedTaskView,
+    public void createTaskDismissAnimation(PendingAnimation anim, TaskView dismissedTaskView,
             boolean animateTaskView, boolean shouldRemoveTask, long duration,
             boolean dismissingForSplitSelection) {
         if (mPendingAnimation != null) {
             mPendingAnimation.createPlaybackController().dispatchOnCancel().dispatchOnEnd();
         }
-        PendingAnimation anim = new PendingAnimation(duration);
 
         int count = getPageCount();
         if (count == 0) {
-            return anim;
+            return;
         }
 
         boolean showAsGrid = showAsGrid();
@@ -3043,11 +3130,35 @@
                 // Animate task with index >= dismissed index and in the same row as the
                 // dismissed index or next focused index. Offset successive task dismissal
                 // durations for a staggered effect.
-                float animationStartProgress = Utilities.boundToRange(
-                        INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
-                                + ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
-                                * ++distanceFromDismissedTask, 0f,
-                        dismissTranslationInterpolationEnd);
+                distanceFromDismissedTask++;
+                boolean isStagingFocusedTask =
+                        isFocusedTaskDismissed && nextFocusedTaskView == null;
+                int staggerColumn =  isStagingFocusedTask
+                        ? (int) Math.ceil(distanceFromDismissedTask / 2f)
+                        : distanceFromDismissedTask;
+                // Set timings based on if user is initiating splitscreen on the focused task,
+                // or splitting/dismissing some other task.
+                SplitAnimationTimings timings = SplitAnimationTimings.OVERVIEW_TO_SPLIT;
+                float animationStartProgress = isStagingFocusedTask
+                        ? Utilities.boundToRange(
+                                timings.getGridSlideStartOffset()
+                                        + (timings.getGridSlideStaggerOffset() * staggerColumn),
+                        0f,
+                        dismissTranslationInterpolationEnd)
+                        : Utilities.boundToRange(
+                                INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
+                                        + ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
+                                        * staggerColumn, 0f, dismissTranslationInterpolationEnd);
+                float animationEndProgress = isStagingFocusedTask
+                        ? Utilities.boundToRange(
+                                timings.getGridSlideStartOffset()
+                                        + (timings.getGridSlideStaggerOffset() * staggerColumn)
+                                        + timings.getGridSlideDurationOffset(),
+                        0f,
+                        dismissTranslationInterpolationEnd)
+                        : dismissTranslationInterpolationEnd;
+                Interpolator dismissInterpolator = isStagingFocusedTask ? OVERSHOOT_0_75 : LINEAR;
+
                 if (taskView == nextFocusedTaskView) {
                     // Enlarge the task to be focused next, and translate into focus position.
                     float scale = mTaskWidth / (float) mLastComputedGridTaskSize.width();
@@ -3070,7 +3181,7 @@
                 } else {
                     float primaryTranslation =
                             nextFocusedTaskView != null ? nextFocusedTaskWidth : dismissedTaskWidth;
-                    if (isFocusedTaskDismissed && nextFocusedTaskView == null) {
+                    if (isStagingFocusedTask) {
                         // Moves less if focused task is not in scroll position.
                         int focusedTaskScroll = getScrollForPage(dismissedIndex);
                         int primaryScroll = mOrientationHandler.getPrimaryScroll(this);
@@ -3086,8 +3197,8 @@
 
                     anim.setFloat(taskView, taskView.getPrimaryDismissTranslationProperty(),
                             mIsRtl ? primaryTranslation : -primaryTranslation,
-                            clampToProgress(LINEAR, animationStartProgress,
-                                    dismissTranslationInterpolationEnd));
+                            clampToProgress(dismissInterpolator, animationStartProgress,
+                                    animationEndProgress));
                 }
             }
         }
@@ -3096,7 +3207,9 @@
             anim.addOnFrameCallback(this::updateCurveProperties);
         }
 
-        // Add a tiny bit of translation Z, so that it draws on top of other views
+        // Add a tiny bit of translation Z, so that it draws on top of other views. This is relevant
+        // (e.g.) when we dismiss a task by sliding it upward: if there is a row of icons above, we
+        // want the dragged task to stay above all other views.
         if (animateTaskView) {
             dismissedTaskView.setTranslationZ(0.1f);
         }
@@ -3317,7 +3430,6 @@
                 mPendingAnimation = null;
             }
         });
-        return anim;
     }
 
     /**
@@ -3329,11 +3441,11 @@
     private void updateCurrentTaskActionsVisibility() {
         boolean isCurrentSplit = getCurrentPageTaskView() instanceof GroupedTaskView;
         mActionsView.updateHiddenFlags(HIDDEN_SPLIT_SCREEN, isCurrentSplit);
-        if (isCurrentSplit) {
-            return;
-        }
-        mActionsView.setSplitButtonVisible(
-                mActivity.getDeviceProfile().isTablet && getTaskViewCount() > 1);
+        mActionsView.updateHiddenFlags(HIDDEN_SPLIT_SELECT_ACTIVE, isSplitSelectionActive());
+        mActionsView.updateSplitButtonFlags(FLAG_IS_NOT_TABLET,
+                !mActivity.getDeviceProfile().isTablet);
+        mActionsView.updateSplitButtonFlags(FLAG_SINGLE_TASK, getTaskViewCount() <= 1);
+        mActionsView.updateSplitButtonVisibility();
     }
 
     /**
@@ -3485,8 +3597,10 @@
     }
 
     public void dismissTask(TaskView taskView, boolean animateTaskView, boolean removeTask) {
-        runDismissAnimation(createTaskDismissAnimation(taskView, animateTaskView, removeTask,
-                DISMISS_TASK_DURATION, false /* dismissingForSplitSelection*/));
+        PendingAnimation pa = new PendingAnimation(DISMISS_TASK_DURATION);
+        createTaskDismissAnimation(pa, taskView, animateTaskView, removeTask, DISMISS_TASK_DURATION,
+                false /* dismissingForSplitSelection*/);
+        runDismissAnimation(pa);
     }
 
     @SuppressWarnings("unused")
@@ -3995,36 +4109,46 @@
         }
     }
 
+    /**
+     * Primarily used by overview actions to initiate split from focused task, logs the source
+     * of split invocation as such.
+     */
     public void initiateSplitSelect(TaskView taskView) {
         int defaultSplitPosition = mOrientationHandler
                 .getDefaultSplitPosition(mActivity.getDeviceProfile());
-        initiateSplitSelect(taskView, defaultSplitPosition);
+        initiateSplitSelect(taskView, defaultSplitPosition, LAUNCHER_OVERVIEW_ACTIONS_SPLIT);
     }
 
-    public void initiateSplitSelect(TaskView taskView, @StagePosition int stagePosition) {
+    public void initiateSplitSelect(TaskView taskView, @StagePosition int stagePosition,
+            StatsLogManager.EventEnum splitEvent) {
         mSplitHiddenTaskView = taskView;
         mSplitSelectStateController.setInitialTaskSelect(taskView.getTask().key.id,
-                stagePosition);
+                stagePosition, splitEvent, taskView.getItemInfo());
         mSplitHiddenTaskViewIndex = indexOfChild(taskView);
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            finishRecentsAnimation(true, null);
+            finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
+                    null /* onFinishComplete */);
         }
     }
 
     public void initiateSplitSelect(QuickstepSystemShortcut.SplitSelectSource splitSelectSource) {
         mSplitSelectSource = splitSelectSource;
         mSplitSelectStateController.setInitialTaskSelect(splitSelectSource.intent,
-                splitSelectSource.position.stagePosition);
+                splitSelectSource.position.stagePosition, splitSelectSource.mItemInfo,
+                splitSelectSource.splitEvent);
     }
 
-    public PendingAnimation createSplitSelectInitAnimation(int duration) {
+    /**
+     * Modifies a PendingAnimation with the animations for entering split staging
+     */
+    public void createSplitSelectInitAnimation(PendingAnimation builder, int duration) {
         if (mSplitHiddenTaskView != null) {
-            return createTaskDismissAnimation(mSplitHiddenTaskView, true, false, duration,
+            // Splitting from Overview
+            createTaskDismissAnimation(builder, mSplitHiddenTaskView, true, false, duration,
                     true /* dismissingForSplitSelection*/);
         } else {
-            PendingAnimation anim = new PendingAnimation(duration);
-            createInitialSplitSelectAnimation(anim);
-            return anim;
+            // Splitting from Home
+            createInitialSplitSelectAnimation(builder);
         }
     }
 
@@ -4055,9 +4179,9 @@
         // TODO(194414938) starting bounds seem slightly off, investigate
         Rect firstTaskStartingBounds = new Rect();
         Rect firstTaskEndingBounds = mTempRect;
-        int duration = mActivity.getStateManager().getState().getTransitionDuration(mActivity,
-                false /* isToState */);
-        PendingAnimation pendingAnimation = new PendingAnimation(duration);
+        PendingAnimation pendingAnimation =
+                new PendingAnimation(SplitAnimationTimings.CONFIRM_DURATION);
+        SplitAnimationTimings timings = SplitAnimationTimings.SPLIT_TO_CONFIRM;
 
         int halfDividerSize = getResources()
                 .getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2;
@@ -4067,30 +4191,37 @@
                 secondTaskEndingBounds);
 
         mFirstFloatingTaskView.getBoundsOnScreen(firstTaskStartingBounds);
-        mFirstFloatingTaskView.addAnimation(pendingAnimation,
+        mFirstFloatingTaskView.addConfirmAnimation(pendingAnimation,
                 new RectF(firstTaskStartingBounds), firstTaskEndingBounds,
                 false /* fadeWithThumbnail */, true /* isStagedTask */);
 
+        safeRemoveDragLayerView(mSecondFloatingTaskView);
         mSecondFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
                 thumbnailView, thumbnailView.getThumbnail(),
                 iconView.getDrawable(), secondTaskStartingBounds);
         mSecondFloatingTaskView.setAlpha(1);
-        mSecondFloatingTaskView.addAnimation(pendingAnimation, secondTaskStartingBounds,
+        mSecondFloatingTaskView.addConfirmAnimation(pendingAnimation, secondTaskStartingBounds,
                 secondTaskEndingBounds, true /* fadeWithThumbnail */, false /* isStagedTask */);
+
+        pendingAnimation.setViewAlpha(mSplitInstructionsView, 0, clampToProgress(LINEAR,
+                timings.getInstructionsFadeStartOffset(),
+                timings.getInstructionsFadeEndOffset()));
+
         pendingAnimation.addEndListener(aBoolean -> {
             mSplitSelectStateController.launchSplitTasks(
                     aBoolean1 -> RecentsView.this.resetFromSplitSelectionState());
             InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
         });
-        if (containerTaskView.containsMultipleTasks()) {
-            // If we are launching from a child task, then only hide the thumbnail itself
-            mSecondSplitHiddenView = thumbnailView;
-        } else {
-            mSecondSplitHiddenView = containerTaskView;
-        }
-        mSecondSplitHiddenView.setVisibility(INVISIBLE);
+
+        mSecondSplitHiddenView = containerTaskView;
+        mSecondSplitHiddenView.setThumbnailVisibility(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,19 +4230,20 @@
     @SuppressLint("WrongCall")
     protected void resetFromSplitSelectionState() {
         if (mSplitSelectSource != null || mSplitHiddenTaskViewIndex != -1) {
-            if (mFirstFloatingTaskView != null) {
-                mActivity.getRootView().removeView(mFirstFloatingTaskView);
-                mFirstFloatingTaskView = null;
-            }
-            if (mSecondFloatingTaskView != null) {
-                mActivity.getRootView().removeView(mSecondFloatingTaskView);
-                mSecondFloatingTaskView = null;
-                mSecondSplitHiddenView.setVisibility(VISIBLE);
-                mSecondSplitHiddenView = null;
-            }
+            safeRemoveDragLayerView(mFirstFloatingTaskView);
+            safeRemoveDragLayerView(mSecondFloatingTaskView);
+            safeRemoveDragLayerView(mSplitInstructionsView);
+            mFirstFloatingTaskView = null;
+            mSecondFloatingTaskView = null;
+            mSplitInstructionsView = null;
             mSplitSelectSource = null;
         }
 
+        if (mSecondSplitHiddenView != null) {
+            mSecondSplitHiddenView.setThumbnailVisibility(VISIBLE);
+            mSecondSplitHiddenView = null;
+        }
+
         if (mSplitHiddenTaskViewIndex == -1) {
             return;
         }
@@ -4127,9 +4259,11 @@
         onLayout(false /*  changed */, getLeft(), getTop(), getRight(), getBottom());
         resetTaskVisuals();
         mSplitHiddenTaskViewIndex = -1;
-        if (mSplitHiddenTaskView != null) {
-            mSplitHiddenTaskView.setVisibility(VISIBLE);
-            mSplitHiddenTaskView = null;
+    }
+
+    private void safeRemoveDragLayerView(@Nullable View viewToRemove) {
+        if (viewToRemove != null) {
+            mActivity.getDragLayer().removeView(viewToRemove);
         }
     }
 
@@ -4138,7 +4272,7 @@
      * Note that the translation can be its primary or secondary dimension.
      */
     public float getSplitSelectTranslation() {
-        int splitPosition = getSplitPlaceholder().getActiveSplitStagePosition();
+        int splitPosition = getSplitSelectController().getActiveSplitStagePosition();
         if (!shouldShiftThumbnailsForSplitSelect()) {
             return 0f;
         }
@@ -4164,6 +4298,10 @@
         taskViewsFloat.first.set(this, getSplitSelectTranslation());
         taskViewsFloat.second.set(this, 0f);
 
+        if (mSplitInstructionsView != null) {
+            mSplitInstructionsView.ensureProperRotation();
+        }
+
         applySplitPrimaryScrollOffset();
     }
 
@@ -4238,30 +4376,33 @@
      * If launching one of the adjacent tasks, parallax the center task and other adjacent task
      * to the right.
      */
+    @SuppressLint("Recycle")
     public AnimatorSet createAdjacentPageAnimForTaskLaunch(TaskView tv) {
         AnimatorSet anim = new AnimatorSet();
 
         int taskIndex = indexOfChild(tv);
         int centerTaskIndex = getCurrentPage();
-        boolean launchingCenterTask = taskIndex == centerTaskIndex;
 
         float toScale = getMaxScaleForFullScreen();
-        RecentsView recentsView = tv.getRecentsView();
+        boolean showAsGrid = showAsGrid();
+        boolean launchingCenterTask = showAsGrid
+                ? tv.isFocusedTask() && isTaskViewFullyVisible(tv)
+                : taskIndex == centerTaskIndex;
         if (launchingCenterTask) {
-            anim.play(ObjectAnimator.ofFloat(recentsView, RECENTS_SCALE_PROPERTY, toScale));
-            anim.play(ObjectAnimator.ofFloat(recentsView, FULLSCREEN_PROGRESS, 1));
-        } else {
+            anim.play(ObjectAnimator.ofFloat(this, RECENTS_SCALE_PROPERTY, toScale));
+            anim.play(ObjectAnimator.ofFloat(this, FULLSCREEN_PROGRESS, 1));
+        } else if (!showAsGrid) {
             // We are launching an adjacent task, so parallax the center and other adjacent task.
             float displacementX = tv.getWidth() * (toScale - 1f);
             float primaryTranslation = mIsRtl ? -displacementX : displacementX;
             anim.play(ObjectAnimator.ofFloat(getPageAt(centerTaskIndex),
                     mOrientationHandler.getPrimaryViewTranslate(), primaryTranslation));
-            int runningTaskIndex = recentsView.getRunningTaskIndex();
+            int runningTaskIndex = getRunningTaskIndex();
             if (ENABLE_QUICKSTEP_LIVE_TILE.get()
                     && runningTaskIndex != -1
                     && runningTaskIndex != taskIndex
-                    && recentsView.getRemoteTargetHandles() != null) {
-                for (RemoteTargetHandle remoteHandle : recentsView.getRemoteTargetHandles()) {
+                    && getRemoteTargetHandles() != null) {
+                for (RemoteTargetHandle remoteHandle : getRemoteTargetHandles()) {
                     anim.play(ObjectAnimator.ofFloat(
                             remoteHandle.getTaskViewSimulator().taskPrimaryTranslation,
                             AnimatedFloat.VALUE,
@@ -4281,6 +4422,7 @@
                         properties));
             }
         }
+        anim.play(ObjectAnimator.ofFloat(this, TASK_THUMBNAIL_SPLASH_ALPHA, 0, 1));
         return anim;
     }
 
@@ -4336,10 +4478,12 @@
 
         DepthController depthController = getDepthController();
         if (depthController != null) {
-            ObjectAnimator depthAnimator = ObjectAnimator.ofFloat(depthController, DEPTH,
+            ObjectAnimator depthAnimator = ObjectAnimator.ofFloat(depthController, STATE_DEPTH,
                     BACKGROUND_APP.getDepth(mActivity));
             anim.play(depthAnimator);
         }
+        anim.play(ObjectAnimator.ofFloat(this, TASK_THUMBNAIL_SPLASH_ALPHA, 0f, 1f));
+
         anim.play(progressAnim);
         anim.setInterpolator(interpolator);
 
@@ -4473,7 +4617,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 +4667,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 +4687,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 +4998,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));
         }
     }
 
@@ -5232,6 +5371,16 @@
         return mRecentsAnimationController;
     }
 
+    @Nullable
+    public FloatingTaskView getFirstFloatingTaskView() {
+        return mFirstFloatingTaskView;
+    }
+
+    @Nullable
+    public SplitInstructionsView getSplitInstructionsView() {
+        return mSplitInstructionsView;
+    }
+
     /** Update the current activity locus id to show the enabled state of Overview */
     public void updateLocusId() {
         String locusId = "Overview";
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..27ec01a
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
@@ -0,0 +1,136 @@
+/*
+ * 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.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 androidx.appcompat.widget.AppCompatTextView;
+
+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;
+    private AppCompatTextView mTextView;
+
+    public static final FloatProperty<SplitInstructionsView> UNFOLD =
+            new FloatProperty<SplitInstructionsView>("SplitInstructionsUnfold") {
+                @Override
+                public void setValue(SplitInstructionsView splitInstructionsView, float v) {
+                    splitInstructionsView.setScaleY(v);
+                }
+
+                @Override
+                public Float get(SplitInstructionsView splitInstructionsView) {
+                    return splitInstructionsView.getScaleY();
+                }
+            };
+
+    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
+                );
+
+        splitInstructionsView.mTextView = splitInstructionsView.findViewById(
+                R.id.split_instructions_text);
+
+        // Since textview overlays base view, and we sometimes manipulate the alpha of each
+        // simultaneously, force overlapping rendering to false prevents redrawing of pixels,
+        // improving performance at the cost of some accuracy.
+        splitInstructionsView.forceHasOverlappingRendering(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;
+        }
+    }
+
+    public AppCompatTextView getTextView() {
+        return mTextView;
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java b/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java
index 28080d4..08004dc 100644
--- a/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java
+++ b/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java
@@ -22,7 +22,6 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
-import android.util.FloatProperty;
 import android.util.TypedValue;
 import android.widget.FrameLayout;
 
@@ -33,20 +32,6 @@
     private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
     private final Rect mTempRect = new Rect();
 
-    public static final FloatProperty<SplitPlaceholderView> ALPHA_FLOAT =
-            new FloatProperty<SplitPlaceholderView>("SplitViewAlpha") {
-                @Override
-                public void setValue(SplitPlaceholderView splitPlaceholderView, float v) {
-                    splitPlaceholderView.setVisibility(v != 0 ? VISIBLE : GONE);
-                    splitPlaceholderView.setAlpha(v);
-                }
-
-                @Override
-                public Float get(SplitPlaceholderView splitPlaceholderView) {
-                    return splitPlaceholderView.getAlpha();
-                }
-            };
-
     @Nullable
     private IconView mIconView;
 
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index 3803f1b..6815745 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -16,9 +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;
 
 import android.animation.Animator;
@@ -157,23 +154,6 @@
                 mTaskContainer.getThumbnailView(), overscrollShift, deviceProfile));
         setY(pagedOrientationHandler.getTaskMenuY(
                 adjustedY, mTaskContainer.getThumbnailView(), overscrollShift));
-
-        // TODO(b/193432925) temporary menu placement for split screen task menus
-        TaskIdAttributeContainer[] taskIdAttributeContainers =
-                mTaskView.getTaskIdAttributeContainers();
-        if (taskIdAttributeContainers[0].getStagePosition() != STAGE_POSITION_UNDEFINED) {
-            if (mTaskContainer.getStagePosition() != STAGE_POSITION_BOTTOM_OR_RIGHT) {
-                return;
-            }
-            Rect r = new Rect();
-            mTaskContainer.getThumbnailView().getBoundsOnScreen(r);
-            if (deviceProfile.isLandscape) {
-                setX(r.left);
-            } else {
-                setY(r.top);
-
-            }
-        }
     }
 
     public void onRotationChanged() {
@@ -232,8 +212,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 +224,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..d7a8599 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -16,11 +16,11 @@
 
 package com.android.quickstep.views;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
 
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_FULLSCREEN;
 
 import android.content.Context;
 import android.graphics.Bitmap;
@@ -36,12 +36,14 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Shader;
+import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
 import android.util.Property;
 import android.view.Surface;
 import android.view.View;
+import android.widget.ImageView;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
@@ -50,8 +52,10 @@
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.touch.PagedOrientationHandler;
 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;
@@ -63,6 +67,7 @@
 public class TaskThumbnailView extends View {
     private static final MainThreadInitializedObject<FullscreenDrawParams> TEMP_PARAMS =
             new MainThreadInitializedObject<>(FullscreenDrawParams::new);
+    private static final float MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT = 0.1f;
 
     public static final Property<TaskThumbnailView, Float> DIM_ALPHA =
             new FloatProperty<TaskThumbnailView>("dimAlpha") {
@@ -82,6 +87,7 @@
     private TaskOverlay mOverlay;
     private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
     private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+    private final Paint mSplashBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
     private final Paint mClearPaint = new Paint();
     private final Paint mDimmingPaintAfterClearing = new Paint();
     private final int mDimColor;
@@ -90,6 +96,8 @@
     private final Rect mPreviewRect = new Rect();
     private final PreviewPositionHelper mPreviewPositionHelper = new PreviewPositionHelper();
     private TaskView.FullscreenDrawParams mFullscreenParams;
+    private ImageView mSplashView;
+    private Drawable mSplashViewDrawable;
 
     @Nullable
     private Task mTask;
@@ -100,6 +108,8 @@
 
     /** How much this thumbnail is dimmed, 0 not dimmed at all, 1 totally dimmed. */
     private float mDimAlpha = 0f;
+    /** Controls visibility of the splash view, 0 is transparent, 255 fully opaque. */
+    private int mSplashAlpha = 0;
 
     private boolean mOverlayEnabled;
 
@@ -115,6 +125,7 @@
         super(context, attrs, defStyleAttr);
         mPaint.setFilterBitmap(true);
         mBackgroundPaint.setColor(Color.WHITE);
+        mSplashBackgroundPaint.setColor(Color.WHITE);
         mClearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
         mActivity = BaseActivity.fromContext(context);
         // Initialize with placeholder value. It is overridden later by TaskView
@@ -134,6 +145,8 @@
         int color = task == null ? Color.BLACK : task.colorBackground | 0xFF000000;
         mPaint.setColor(color);
         mBackgroundPaint.setColor(color);
+        mSplashBackgroundPaint.setColor(color);
+        updateSplashView(mTask.icon);
     }
 
     /**
@@ -151,6 +164,9 @@
         boolean thumbnailWasNull = mThumbnailData == null;
         mThumbnailData =
                 (thumbnailData != null && thumbnailData.thumbnail != null) ? thumbnailData : null;
+        if (mTask != null) {
+            updateSplashView(mTask.icon);
+        }
         if (refreshNow) {
             refresh(thumbnailWasNull && mThumbnailData != null);
         }
@@ -201,6 +217,18 @@
         updateThumbnailPaintFilter();
     }
 
+    /**
+     * Sets the alpha of the splash view.
+     */
+    public void setSplashAlpha(float splashAlpha) {
+        mSplashAlpha = (int) (Utilities.boundToRange(splashAlpha, 0f, 1f) * 255);
+        if (mSplashViewDrawable != null) {
+            mSplashViewDrawable.setAlpha(mSplashAlpha);
+        }
+        mSplashBackgroundPaint.setAlpha(mSplashAlpha);
+        invalidate();
+    }
+
     public TaskOverlay getTaskOverlay() {
         if (mOverlay == null) {
             mOverlay = getTaskView().getRecentsView().getTaskOverlayFactory().createOverlay(this);
@@ -237,16 +265,13 @@
         boundsToBitmapSpace.mapRect(boundsInBitmapSpace, viewRect);
 
         DeviceProfile dp = mActivity.getDeviceProfile();
-        int leftInset = TaskView.clipLeft(dp) ? Math.round(boundsInBitmapSpace.left) : 0;
-        int topInset = TaskView.clipTop(dp) ? Math.round(boundsInBitmapSpace.top) : 0;
-        int rightInset = TaskView.clipRight(dp) ? Math.round(
-                bitmapRect.right - boundsInBitmapSpace.right) : 0;
-        int bottomInset = TaskView.clipBottom(dp)
+        int bottomInset = dp.isTablet
                 ? Math.round(bitmapRect.bottom - boundsInBitmapSpace.bottom) : 0;
-        return Insets.of(leftInset, topInset, rightInset, bottomInset);
+        return Insets.of(0, 0, 0, bottomInset);
     }
 
 
+    @SystemUiControllerFlags
     public int getSysUiStatusNavFlags() {
         if (mThumbnailData != null) {
             int flags = 0;
@@ -262,6 +287,12 @@
     }
 
     @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        updateSplashView(mSplashViewDrawable);
+    }
+
+    @Override
     protected void onDraw(Canvas canvas) {
         RectF currentDrawnInsets = mFullscreenParams.mCurrentDrawnInsets;
         canvas.save();
@@ -311,6 +342,17 @@
         }
 
         canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mPaint);
+
+        // Draw splash above thumbnail to hide inconsistencies in rotation and aspect ratios.
+        if (shouldShowSplashView()) {
+            if (mSplashView != null) {
+                canvas.drawRoundRect(x, y, width + 1, height + 1, cornerRadius,
+                        cornerRadius, mSplashBackgroundPaint);
+
+                mSplashView.layout((int) x, (int) (y + 1), (int) width, (int) height - 1);
+                mSplashView.draw(canvas);
+            }
+        }
     }
 
     public TaskView getTaskView() {
@@ -326,6 +368,78 @@
     }
 
     /**
+     * Determine if the splash should be shown over top of the thumbnail.
+     *
+     * <p>We want to show the splash if the aspect ratio or rotation of the thumbnail would be
+     * different from the task.
+     */
+    boolean shouldShowSplashView() {
+        return isThumbnailAspectRatioDifferentFromThumbnailData()
+                || isThumbnailRotationDifferentFromTask();
+    }
+
+    private void updateSplashView(Drawable icon) {
+        if (icon == null || icon.getConstantState() == null) {
+            return;
+        }
+        mSplashViewDrawable = icon.getConstantState().newDrawable().mutate();
+        mSplashViewDrawable.setAlpha(mSplashAlpha);
+        ImageView imageView = mSplashView == null ? new ImageView(getContext()) : mSplashView;
+        imageView.setImageDrawable(mSplashViewDrawable);
+
+        imageView.setScaleType(ImageView.ScaleType.MATRIX);
+        Matrix matrix = new Matrix();
+
+        float drawableWidth = mSplashViewDrawable.getIntrinsicWidth();
+        float drawableHeight = mSplashViewDrawable.getIntrinsicHeight();
+        float viewWidth = getMeasuredWidth();
+        float viewCenterX = viewWidth / 2f;
+        float viewHeight = getMeasuredHeight();
+        float viewCenterY = viewHeight / 2f;
+        float centeredDrawableLeft = (viewWidth - drawableWidth) / 2f;
+        float centeredDrawableTop = (viewHeight - drawableHeight) / 2f;
+        float nonGridScale = getTaskView() == null ? 1 : 1 / getTaskView().getNonGridScale();
+        float recentsMaxScale = getTaskView() == null || getTaskView().getRecentsView() == null
+                ? 1 : 1 / getTaskView().getRecentsView().getMaxScaleForFullScreen();
+        float scale = nonGridScale * recentsMaxScale;
+
+        // Center the image in the view.
+        matrix.setTranslate(centeredDrawableLeft, centeredDrawableTop);
+        // Apply scale transformation after translation, pivoting around center of view.
+        matrix.postScale(scale, scale, viewCenterX, viewCenterY);
+
+        imageView.setImageMatrix(matrix);
+        mSplashView = imageView;
+    }
+
+    private boolean isThumbnailAspectRatioDifferentFromThumbnailData() {
+        if (mThumbnailData == null || mThumbnailData.thumbnail == null) {
+            return false;
+        }
+
+        float thumbnailViewAspect = getWidth() / (float) getHeight();
+        float thumbnailDataAspect =
+                mThumbnailData.thumbnail.getWidth() / (float) mThumbnailData.thumbnail.getHeight();
+
+        return Utilities.isRelativePercentDifferenceGreaterThan(thumbnailViewAspect,
+                thumbnailDataAspect, MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT);
+    }
+
+    private boolean isThumbnailRotationDifferentFromTask() {
+        RecentsView recents = getTaskView().getRecentsView();
+        if (recents == null || mThumbnailData == null) {
+            return false;
+        }
+
+        if (recents.getPagedOrientationHandler() == PagedOrientationHandler.PORTRAIT) {
+            int currentRotation = recents.getPagedViewOrientedState().getRecentsActivityRotation();
+            return (currentRotation - mThumbnailData.rotation) % 2 != 0;
+        } else {
+            return recents.getPagedOrientationHandler().getRotation() != mThumbnailData.rotation;
+        }
+    }
+
+    /**
      * Potentially re-init the task overlay. Be cautious when calling this as the overlay may
      * do processing on initialization.
      */
@@ -433,27 +547,17 @@
             int thumbnailRotation = thumbnailData.rotation;
             int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation);
             RectF thumbnailClipHint = new RectF();
-            if (TaskView.clipLeft(dp)) {
-                thumbnailClipHint.left = thumbnailData.insets.left;
-            }
-            if (TaskView.clipRight(dp)) {
-                thumbnailClipHint.right = thumbnailData.insets.right;
-            }
-            if (TaskView.clipTop(dp)) {
-                thumbnailClipHint.top = thumbnailData.insets.top;
-            }
-            if (TaskView.clipBottom(dp)) {
-                thumbnailClipHint.bottom = thumbnailData.insets.bottom;
-            }
+            float canvasScreenRatio = canvasWidth / (float) dp.widthPx;
+            float scaledTaskbarSize = dp.taskbarSize * canvasScreenRatio;
+            thumbnailClipHint.bottom = dp.isTablet ? scaledTaskbarSize : 0;
 
             float scale = thumbnailData.scale;
             final float thumbnailScale;
 
             // 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) {
@@ -475,8 +579,9 @@
                 float availableAspect = isRotated
                         ? availableHeight / availableWidth
                         : availableWidth / availableHeight;
-                boolean isAspectLargelyDifferent = Utilities.isRelativePercentDifferenceGreaterThan(
-                        canvasAspect, availableAspect, 0.1f);
+                boolean isAspectLargelyDifferent =
+                        Utilities.isRelativePercentDifferenceGreaterThan(canvasAspect,
+                                availableAspect, MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT);
                 if (isRotated && isAspectLargelyDifferent) {
                     // Do not rotate thumbnail if it would not improve fit
                     isRotated = false;
@@ -485,18 +590,10 @@
 
                 if (isAspectLargelyDifferent) {
                     // Crop letterbox insets if insets isn't already clipped
-                    if (!TaskView.clipLeft(dp)) {
-                        thumbnailClipHint.left = thumbnailData.letterboxInsets.left;
-                    }
-                    if (!TaskView.clipRight(dp)) {
-                        thumbnailClipHint.right = thumbnailData.letterboxInsets.right;
-                    }
-                    if (!TaskView.clipTop(dp)) {
-                        thumbnailClipHint.top = thumbnailData.letterboxInsets.top;
-                    }
-                    if (!TaskView.clipBottom(dp)) {
-                        thumbnailClipHint.bottom = thumbnailData.letterboxInsets.bottom;
-                    }
+                    thumbnailClipHint.left = thumbnailData.letterboxInsets.left;
+                    thumbnailClipHint.right = thumbnailData.letterboxInsets.right;
+                    thumbnailClipHint.top = thumbnailData.letterboxInsets.top;
+                    thumbnailClipHint.bottom = thumbnailData.letterboxInsets.bottom;
                     availableWidth = surfaceWidth
                             - (thumbnailClipHint.left + thumbnailClipHint.right);
                     availableHeight = surfaceHeight
@@ -559,44 +656,15 @@
                 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);
-            }
+            mClippedInsets.set(0, 0, 0, scaledTaskbarSize);
 
             mMatrix.postScale(thumbnailScale, thumbnailScale);
             mIsOrientationChanged = isOrientationDifferent;
@@ -617,44 +685,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..a81f95f 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -18,8 +18,8 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.widget.Toast.LENGTH_SHORT;
+import static android.window.SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR;
 
-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;
@@ -32,6 +32,7 @@
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 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.launcher3.util.SplitConfigurationOptions.getLogEventForPosition;
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
@@ -43,12 +44,12 @@
 import android.app.ActivityOptions;
 import android.content.Context;
 import android.content.Intent;
-import android.graphics.Outline;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
+import android.os.Handler;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
 import android.util.Log;
@@ -57,7 +58,6 @@
 import android.view.TouchDelegate;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewOutlineProvider;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.animation.Interpolator;
 import android.widget.FrameLayout;
@@ -67,7 +67,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;
@@ -77,7 +76,7 @@
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.testing.TestLogging;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.util.ActivityOptionsWrapper;
 import com.android.launcher3.util.ComponentKey;
@@ -89,7 +88,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;
@@ -97,13 +95,13 @@
 import com.android.quickstep.TaskViewUtils;
 import com.android.quickstep.util.CancellableTask;
 import com.android.quickstep.util.RecentsOrientedState;
+import com.android.quickstep.util.SplitSelectStateController;
 import com.android.quickstep.util.TaskCornerRadius;
 import com.android.quickstep.util.TransformParams;
 import com.android.quickstep.views.TaskThumbnailView.PreviewPositionHelper;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
@@ -139,41 +137,6 @@
     /** The maximum amount that a task view can be scrimmed, dimmed or tinted. */
     public static final float MAX_PAGE_SCRIM_ALPHA = 0.4f;
 
-    /**
-     * Should the TaskView display clip off the left inset in RecentsView.
-     */
-    public static boolean clipLeft(DeviceProfile deviceProfile) {
-        return false;
-    }
-
-    /**
-     * Should the TaskView display clip off the top inset in RecentsView.
-     */
-    public static boolean clipTop(DeviceProfile deviceProfile) {
-        return false;
-    }
-
-    /**
-     * Should the TaskView display clip off the right inset in RecentsView.
-     */
-    public static boolean clipRight(DeviceProfile deviceProfile) {
-        return false;
-    }
-
-    /**
-     * Should the TaskView display clip off the bottom inset in RecentsView.
-     */
-    public static boolean clipBottom(DeviceProfile deviceProfile) {
-        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;
 
@@ -362,8 +325,6 @@
                 }
             };
 
-    private final TaskOutlineProvider mOutlineProvider;
-
     @Nullable
     protected Task mTask;
     protected TaskThumbnailView mSnapshotView;
@@ -371,6 +332,7 @@
     protected final DigitalWellBeingToast mDigitalWellBeingToast;
     private float mFullscreenProgress;
     private float mGridProgress;
+    protected float mTaskThumbnailSplashAlpha;
     private float mNonGridScale = 1;
     private float mDismissScale = 1;
     protected final FullscreenDrawParams mCurrentFullscreenParams;
@@ -426,11 +388,10 @@
 
     private final float[] mIconCenterCoords = new float[2];
 
-    private final PointF mLastTouchDownPosition = new PointF();
+    protected final PointF mLastTouchDownPosition = new PointF();
 
     private boolean mIsClickableAsLiveTile = true;
 
-
     public TaskView(Context context) {
         this(context, null);
     }
@@ -446,10 +407,6 @@
 
         mCurrentFullscreenParams = new FullscreenDrawParams(context);
         mDigitalWellBeingToast = new DigitalWellBeingToast(mActivity, this);
-
-        mOutlineProvider = new TaskOutlineProvider(getContext(), mCurrentFullscreenParams,
-                mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx);
-        setOutlineProvider(mOutlineProvider);
     }
 
     public void setTaskViewId(int id) {
@@ -605,6 +562,18 @@
 
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
+        RecentsView recentsView = getRecentsView();
+        if (recentsView == null || mTask == null) {
+            return false;
+        }
+        SplitSelectStateController splitSelectStateController =
+                recentsView.getSplitSelectController();
+        if (splitSelectStateController.isSplitSelectActive() &&
+                splitSelectStateController.getInitialTaskId() == mTask.key.id) {
+            // Prevent taps on the this taskview if it's being animated into split select state
+            return false;
+        }
+
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
             mLastTouchDownPosition.set(ev.getX(), ev.getY());
         }
@@ -628,16 +597,16 @@
      *         second app. {@code false} otherwise
      */
     private boolean confirmSecondSplitSelectApp() {
-        int index = getChildTaskIndexAtPosition(mLastTouchDownPosition);
+        int index = getLastSelectedChildTaskIndex();
         TaskIdAttributeContainer container = mTaskIdAttributeContainer[index];
         return getRecentsView().confirmSplitSelect(this, container.getTask(),
                 container.getIconView(), container.getThumbnailView());
     }
 
     /**
-     * Returns the task under the given position in the local coordinates of this task view.
+     * Returns the task index of the last selected child task (0 or 1).
      */
-    protected int getChildTaskIndexAtPosition(PointF position) {
+    protected int getLastSelectedChildTaskIndex() {
         return 0;
     }
 
@@ -691,13 +660,16 @@
                     TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", mTask);
 
             // Indicate success once the system has indicated that the transition has started
-            ActivityOptions opts = ActivityOptionsCompat.makeCustomAnimation(
-                    getContext(), 0, 0, () -> callback.accept(true), MAIN_EXECUTOR.getHandler());
+            ActivityOptions opts = makeCustomAnimation(getContext(), 0, 0,
+                    () -> callback.accept(true), MAIN_EXECUTOR.getHandler());
             opts.setLaunchDisplayId(
                     getDisplay() == null ? DEFAULT_DISPLAY : getDisplay().getDisplayId());
             if (freezeTaskList) {
-                ActivityOptionsCompat.setFreezeRecentTasksList(opts);
+                opts.setFreezeRecentTasksReordering();
             }
+            // TODO(b/202826469): Replace setSplashScreenStyle with setDisableStartingWindow.
+            opts.setSplashScreenStyle(mSnapshotView.shouldShowSplashView()
+                    ? SPLASH_SCREEN_STYLE_SOLID_COLOR : opts.getSplashScreenStyle());
             Task.TaskKey key = mTask.key;
             UI_HELPER_EXECUTOR.execute(() -> {
                 if (!ActivityManagerWrapper.getInstance().startActivityFromRecents(key, opts)) {
@@ -716,20 +688,31 @@
     }
 
     /**
+     * Returns ActivityOptions for overriding task transition animation.
+     */
+    private ActivityOptions makeCustomAnimation(Context context, int enterResId,
+            int exitResId, final Runnable callback, final Handler callbackHandler) {
+        return ActivityOptions.makeCustomTaskAnimation(context, enterResId, exitResId,
+                callbackHandler,
+                elapsedRealTime -> {
+                    if (callback != null) {
+                        callbackHandler.post(callback);
+                    }
+                }, null /* finishedListener */);
+    }
+
+    /**
      * Launch of the current task (both live and inactive tasks) with an animation.
      */
-    public void launchTasks() {
+    public RunnableList launchTasks() {
         RecentsView recentsView = getRecentsView();
         RemoteTargetHandle[] remoteTargetHandles = recentsView.mRemoteTargetHandles;
+        RunnableList runnableList = new RunnableList();
         if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask() && remoteTargetHandles != null) {
             if (!mIsClickableAsLiveTile) {
-                return;
+                return runnableList;
             }
 
-            // 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) {
@@ -754,7 +737,7 @@
                 // here, try to launch the task as a non live tile task.
                 launchTaskAnimated();
                 mIsClickableAsLiveTile = true;
-                return;
+                return runnableList;
             }
 
             AnimatorSet anim = new AnimatorSet();
@@ -779,13 +762,24 @@
                         launchTaskAnimated();
                     }
                     mIsClickableAsLiveTile = true;
+                    runEndCallback();
+                }
+
+                @Override
+                public void onAnimationCancel(Animator animation) {
+                    runEndCallback();
+                }
+
+                private void runEndCallback() {
+                    runnableList.executeAllAndDestroy();
                 }
             });
             anim.start();
             recentsView.onTaskLaunchedInLiveTileMode();
         } else {
-            launchTaskAnimated();
+            return launchTaskAnimated();
         }
+        return runnableList;
     }
 
     /**
@@ -912,10 +906,9 @@
 
         int thumbnailTopMargin = deviceProfile.overviewTaskThumbnailTopMarginPx;
         int taskIconHeight = deviceProfile.overviewTaskIconSizePx;
-        int taskMargin = isGridTask ? deviceProfile.overviewTaskMarginGridPx
-                : deviceProfile.overviewTaskMarginPx;
-        int taskIconMargin = thumbnailTopMargin - taskIconHeight - taskMargin;
-        orientationHandler.setTaskIconParams(iconParams, taskIconMargin, taskIconHeight,
+        int taskMargin = deviceProfile.overviewTaskMarginPx;
+
+        orientationHandler.setTaskIconParams(iconParams, taskMargin, taskIconHeight,
                 thumbnailTopMargin, isRtl);
         iconParams.width = iconParams.height = taskIconHeight;
         mIconView.setLayoutParams(iconParams);
@@ -1102,6 +1095,18 @@
         return scale;
     }
 
+    /**
+     * Updates alpha of task thumbnail splash on swipe up/down.
+     */
+    public void setTaskThumbnailSplashAlpha(float taskThumbnailSplashAlpha) {
+        mTaskThumbnailSplashAlpha = taskThumbnailSplashAlpha;
+        applyThumbnailSplashAlpha();
+    }
+
+    protected void applyThumbnailSplashAlpha() {
+        mSnapshotView.setSplashAlpha(mTaskThumbnailSplashAlpha);
+    }
+
     private void setSplitSelectTranslationX(float x) {
         mSplitSelectTranslationX = x;
         applyTranslationX();
@@ -1289,33 +1294,6 @@
         mEndQuickswitchCuj = endQuickswitchCuj;
     }
 
-    private static final class TaskOutlineProvider extends ViewOutlineProvider {
-
-        private int mMarginTop;
-        private FullscreenDrawParams mFullscreenParams;
-
-        TaskOutlineProvider(Context context, FullscreenDrawParams fullscreenParams, int topMargin) {
-            mMarginTop = topMargin;
-            mFullscreenParams = fullscreenParams;
-        }
-
-        public void updateParams(FullscreenDrawParams params, int topMargin) {
-            mFullscreenParams = params;
-            mMarginTop = topMargin;
-        }
-
-        @Override
-        public void getOutline(View view, Outline outline) {
-            RectF insets = mFullscreenParams.mCurrentDrawnInsets;
-            float scale = mFullscreenParams.mScale;
-            outline.setRoundRect(0,
-                    (int) (mMarginTop * scale),
-                    (int) ((insets.left + view.getWidth() + insets.right) * scale),
-                    (int) ((insets.top + view.getHeight() + insets.bottom) * scale),
-                    mFullscreenParams.mCurrentDrawnCornerRadius);
-        }
-    }
-
     private int getExpectedViewHeight(View view) {
         int expectedHeight;
         int h = view.getLayoutParams().height;
@@ -1343,7 +1321,7 @@
                 continue;
             }
             for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this,
-                    mActivity.getDeviceProfile(), taskContainer)) {
+                    taskContainer)) {
                 info.addAction(s.createAccessibilityAction(context));
             }
         }
@@ -1381,7 +1359,7 @@
                 continue;
             }
             for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this,
-                    mActivity.getDeviceProfile(), taskContainer)) {
+                    taskContainer)) {
                 if (s.hasHandlerForAction(action)) {
                     s.onClick(this);
                     return true;
@@ -1421,11 +1399,6 @@
         mSnapshotView.getTaskOverlay().setFullscreenProgress(progress);
 
         updateSnapshotRadius();
-
-        mOutlineProvider.updateParams(
-                mCurrentFullscreenParams,
-                mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx);
-        invalidateOutline();
     }
 
     protected void updateSnapshotRadius() {
@@ -1537,8 +1510,8 @@
     }
 
     public void initiateSplitSelect(SplitPositionOption splitPositionOption) {
-        AbstractFloatingView.closeOpenViews(mActivity, false, TYPE_TASK_MENU);
-        getRecentsView().initiateSplitSelect(this, splitPositionOption.stagePosition);
+        getRecentsView().initiateSplitSelect(this, splitPositionOption.stagePosition,
+                getLogEventForPosition(splitPositionOption.stagePosition));
     }
 
     /**
@@ -1557,6 +1530,19 @@
     }
 
     /**
+     *     Sets visibility for the thumbnail and associated elements (DWB banners and action chips).
+     *     IconView is unaffected.
+     */
+    void setThumbnailVisibility(int visibility) {
+        for (int i = 0; i < getChildCount(); i++) {
+            View child = getChildAt(i);
+            if (child != mIconView) {
+                child.setVisibility(visibility);
+            }
+        }
+    }
+
+    /**
      * We update and subsequently draw these in {@link #setFullscreenProgress(float)}.
      */
     public static class FullscreenDrawParams {
@@ -1584,17 +1570,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/AbstractQuickStepTest.java b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
index 09f0858..f190e27 100644
--- a/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
+++ b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
@@ -35,7 +35,7 @@
  * Base class for all instrumentation tests that deal with Quickstep.
  */
 public abstract class AbstractQuickStepTest extends AbstractLauncherUiTest {
-    static final boolean ENABLE_SHELL_TRANSITIONS =
+    public static final boolean ENABLE_SHELL_TRANSITIONS =
             SystemProperties.getBoolean("persist.wm.debug.shell_transit", false);
     @Override
     protected TestRule getRulesInsideActivityMonitor() {
diff --git a/quickstep/tests/src/com/android/quickstep/HotseatWidthCalculationTest.kt b/quickstep/tests/src/com/android/quickstep/HotseatWidthCalculationTest.kt
new file mode 100644
index 0000000..3967f75
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/HotseatWidthCalculationTest.kt
@@ -0,0 +1,163 @@
+/*
+ * 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 android.graphics.Rect
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.DeviceProfileBaseTest
+import com.android.launcher3.util.WindowBounds
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class HotseatWidthCalculationTest : DeviceProfileBaseTest() {
+
+    /**
+     * This is a case when after setting the hotseat, the space needs to be recalculated
+     * but it doesn't need to change QSB width or remove icons
+     */
+    @Test
+    fun distribute_border_space_when_space_is_enough_portrait() {
+        initializeVarsForTablet(isGestureMode = false)
+        windowBounds = WindowBounds(Rect(0, 0, 1800, 2560), Rect(0, 104, 0, 0))
+        val dp = newDP()
+        dp.isTaskbarPresentInApps = true
+
+        assertThat(dp.hotseatBarEndOffset).isEqualTo(558)
+        assertThat(dp.numShownHotseatIcons).isEqualTo(6)
+        assertThat(dp.hotseatBorderSpace).isEqualTo(69)
+
+        assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(176)
+        assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(558)
+
+        assertThat(dp.isQsbInline).isFalse()
+        assertThat(dp.hotseatQsbWidth).isEqualTo(1445)
+    }
+
+    /**
+     * This is a case when after setting the hotseat, and recalculating spaces
+     * it still needs to remove icons for everything to fit
+     */
+    @Test
+    fun decrease_num_of_icons_when_not_enough_space_portrait() {
+        initializeVarsForTablet(isGestureMode = false)
+        windowBounds = WindowBounds(Rect(0, 0, 1300, 2560), Rect(0, 104, 0, 0))
+        val dp = newDP()
+        dp.isTaskbarPresentInApps = true
+
+        assertThat(dp.hotseatBarEndOffset).isEqualTo(558)
+        assertThat(dp.numShownHotseatIcons).isEqualTo(4)
+        assertThat(dp.hotseatBorderSpace).isEqualTo(76)
+
+        assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(122)
+        assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(558)
+
+        assertThat(dp.isQsbInline).isFalse()
+        assertThat(dp.hotseatQsbWidth).isEqualTo(1058)
+    }
+
+    /**
+     * This is a case when after setting the hotseat, the space needs to be recalculated
+     * but it doesn't need to change QSB width or remove icons
+     */
+    @Test
+    fun distribute_border_space_when_space_is_enough_landscape() {
+        initializeVarsForTwoPanel(isGestureMode = false, isLandscape = true)
+        val dp = newDP()
+        dp.isTaskbarPresentInApps = true
+
+        assertThat(dp.hotseatBarEndOffset).isEqualTo(744)
+        assertThat(dp.numShownHotseatIcons).isEqualTo(6)
+        assertThat(dp.hotseatBorderSpace).isEqualTo(83)
+
+        assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(106)
+        assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(744)
+
+        assertThat(dp.isQsbInline).isFalse()
+        assertThat(dp.hotseatQsbWidth).isEqualTo(1467)
+    }
+
+    /**
+     * This is a case when the hotseat spans a certain amount of columns
+     * and the nav buttons push the hotseat to the side, but not enough to change the border space.
+     */
+    @Test
+    fun nav_buttons_dont_interfere_with_required_hotseat_width() {
+        initializeVarsForTablet(isGestureMode = false, isLandscape = true)
+        inv?.apply {
+            hotseatColumnSpan = IntArray(4) { 4 }
+            inlineQsb = BooleanArray(4) { false }
+        }
+        val dp = newDP()
+        dp.isTaskbarPresentInApps = true
+
+        assertThat(dp.hotseatBarEndOffset).isEqualTo(705)
+        assertThat(dp.numShownHotseatIcons).isEqualTo(6)
+        assertThat(dp.hotseatBorderSpace).isEqualTo(108)
+
+        assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(631)
+        assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(705)
+
+        assertThat(dp.isQsbInline).isFalse()
+        assertThat(dp.hotseatQsbWidth).isEqualTo(1227)
+    }
+
+    /**
+     * This is a case when after setting the hotseat, the QSB width needs to be changed to fit
+     */
+    @Test
+    fun decrease_qsb_when_not_enough_space_landscape() {
+        initializeVarsForTablet(isGestureMode = false, isLandscape = true)
+        windowBounds = WindowBounds(Rect(0, 0, 2460, 1600), Rect(0, 104, 0, 0))
+        val dp = newDP()
+        dp.isTaskbarPresentInApps = true
+
+        assertThat(dp.hotseatBarEndOffset).isEqualTo(705)
+        assertThat(dp.numShownHotseatIcons).isEqualTo(6)
+        assertThat(dp.hotseatBorderSpace).isEqualTo(36)
+
+        assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(884)
+        assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(705)
+
+        assertThat(dp.isQsbInline).isTrue()
+        assertThat(dp.hotseatQsbWidth).isEqualTo(559)
+    }
+
+    /**
+     * This is a case when after setting the hotseat, changing QSB width, and recalculating spaces
+     * it still needs to remove icons for everything to fit
+     */
+    @Test
+    fun decrease_num_of_icons_when_not_enough_space_landscape() {
+        initializeVarsForTablet(isGestureMode = false, isLandscape = true)
+        windowBounds = WindowBounds(Rect(0, 0, 2260, 1600), Rect(0, 104, 0, 0))
+        val dp = newDP()
+        dp.isTaskbarPresentInApps = true
+
+        assertThat(dp.hotseatBarEndOffset).isEqualTo(705)
+        assertThat(dp.numShownHotseatIcons).isEqualTo(5)
+        assertThat(dp.hotseatBorderSpace).isEqualTo(56)
+
+        assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(801)
+        assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(705)
+
+        assertThat(dp.isQsbInline).isTrue()
+        assertThat(dp.hotseatQsbWidth).isEqualTo(480)
+    }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java b/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
index 9e5d958..9c240f0 100644
--- a/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
+++ b/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
@@ -19,7 +19,7 @@
 
 import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
 
-import static com.android.launcher3.util.DisplayController.NavigationMode.NO_BUTTON;
+import static com.android.launcher3.util.NavigationMode.NO_BUTTON;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -35,14 +35,13 @@
 import android.util.ArrayMap;
 import android.util.DisplayMetrics;
 import android.util.Size;
-import android.view.Display;
 import android.view.MotionEvent;
 import android.view.Surface;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
-import com.android.launcher3.ResourceUtils;
+import com.android.launcher3.testing.shared.ResourceUtils;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.RotationUtils;
 import com.android.launcher3.util.WindowBounds;
@@ -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/RecentTasksListTest.java b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
index 4e49716..ed5526f 100644
--- a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
+++ b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
@@ -26,12 +26,12 @@
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
+import android.app.KeyguardManager;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.launcher3.util.LooperExecutor;
 import com.android.quickstep.util.GroupTask;
-import com.android.systemui.shared.system.KeyguardManagerCompat;
 import com.android.wm.shell.util.GroupedRecentTaskInfo;
 
 import org.junit.Before;
@@ -56,8 +56,8 @@
     public void setup() {
         MockitoAnnotations.initMocks(this);
         LooperExecutor mockMainThreadExecutor = mock(LooperExecutor.class);
-        KeyguardManagerCompat mockKeyguardManagerCompat = mock(KeyguardManagerCompat.class);
-        mRecentTasksList = new RecentTasksList(mockMainThreadExecutor, mockKeyguardManagerCompat,
+        KeyguardManager mockKeyguardManager = mock(KeyguardManager.class);
+        mRecentTasksList = new RecentTasksList(mockMainThreadExecutor, mockKeyguardManager,
                 mockSystemUiProxy);
     }
 
@@ -70,7 +70,7 @@
 
     @Test
     public void loadTasksInBackground_onlyKeys_noValidTaskDescription() {
-        GroupedRecentTaskInfo recentTaskInfos = new GroupedRecentTaskInfo(
+        GroupedRecentTaskInfo recentTaskInfos = GroupedRecentTaskInfo.forSplitTasks(
                 new ActivityManager.RecentTaskInfo(), new ActivityManager.RecentTaskInfo(), null);
         when(mockSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
                 .thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos)));
@@ -90,8 +90,8 @@
         task1.taskDescription = new ActivityManager.TaskDescription(taskDescription);
         ActivityManager.RecentTaskInfo task2 = new ActivityManager.RecentTaskInfo();
         task2.taskDescription = new ActivityManager.TaskDescription();
-        GroupedRecentTaskInfo recentTaskInfos = new GroupedRecentTaskInfo(
-                task1, task2, null);
+        GroupedRecentTaskInfo recentTaskInfos = GroupedRecentTaskInfo.forSplitTasks(task1, task2,
+                null);
         when(mockSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
                 .thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos)));
 
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/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 4bf247c..42e9be3 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -179,6 +179,35 @@
         actionsView.clickAndDismissScreenshot();
     }
 
+    @Test
+    @PortraitLandscape
+    public void testSplitFromOverview() {
+        assumeTrue(!mLauncher.isTablet());
+
+        startTestActivity(2);
+        startTestActivity(3);
+
+        mLauncher.goHome().switchToOverview().getCurrentTask()
+                .tapMenu()
+                .tapSplitMenuItem()
+                .getTestActivityTask(2)
+                .open();
+    }
+
+    @Test
+    @PortraitLandscape
+    public void testSplitFromOverviewForTablet() {
+        assumeTrue(mLauncher.isTablet());
+
+        startTestActivity(2);
+        startTestActivity(3);
+
+        mLauncher.goHome().switchToOverview().getOverviewActions()
+                .clickSplit()
+                .getTestActivityTask(2)
+                .open();
+    }
+
     private int getCurrentOverviewPage(Launcher launcher) {
         return launcher.<RecentsView>getOverviewPanel().getCurrentPage();
     }
@@ -198,7 +227,9 @@
     @Test
     @NavigationModeSwitch
     @PortraitLandscape
+    @ScreenRecord // b/238461765
     public void testSwitchToOverview() throws Exception {
+        startTestAppsWithCheck();
         assertNotNull("Workspace.switchToOverview() returned null",
                 mLauncher.goHome().switchToOverview());
         assertTrue("Launcher internal state didn't switch to Overview",
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java b/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java
index 1df9c02..9337cb5 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java
@@ -68,6 +68,13 @@
     }
 
     @Test
+    public void testHideTaskbarPersistsOnRecreate() {
+        getTaskbar().hide();
+        mLauncher.recreateTaskbar();
+        mLauncher.getLaunchedAppState().assertTaskbarHidden();
+    }
+
+    @Test
     public void testLaunchApp() throws Exception {
         getTaskbar().getAppIcon(TEST_APP_NAME).launch(TEST_APP_PACKAGE);
     }
diff --git a/quickstep/tests/src/com/android/quickstep/TaskThumbnailViewTest.kt b/quickstep/tests/src/com/android/quickstep/TaskThumbnailViewTest.kt
new file mode 100644
index 0000000..cf3c8c9
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/TaskThumbnailViewTest.kt
@@ -0,0 +1,76 @@
+/*
+ * 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 android.graphics.Rect
+import android.graphics.RectF
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.DeviceProfileBaseTest
+import com.android.quickstep.views.TaskThumbnailView.PreviewPositionHelper
+import com.android.systemui.shared.recents.model.ThumbnailData
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+
+/**
+ * Test for TaskThumbnailView class.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TaskThumbnailViewTest : DeviceProfileBaseTest() {
+
+    private var mThumbnailData: ThumbnailData = mock(ThumbnailData::class.java)
+
+    private val mPreviewPositionHelper = PreviewPositionHelper()
+
+    @Test
+    fun getInsetsToDrawInFullscreen_clipTaskbarSizeFromBottomForTablets() {
+        initializeVarsForTablet()
+        val dp = newDP()
+        val previewRect = Rect(0, 0, 100, 100)
+        val canvasWidth = dp.widthPx / 2
+        val canvasHeight = dp.heightPx / 2
+        val currentRotation = 0
+        val isRtl = false
+
+        mPreviewPositionHelper.updateThumbnailMatrix(previewRect, mThumbnailData, canvasWidth,
+                canvasHeight, dp, currentRotation, isRtl)
+
+        val expectedClippedInsets = RectF(0f, 0f, 0f, dp.taskbarSize / 2f)
+        assertThat(mPreviewPositionHelper.getInsetsToDrawInFullscreen(dp))
+                .isEqualTo(expectedClippedInsets)
+    }
+
+    @Test
+    fun getInsetsToDrawInFullscreen_doNotClipTaskbarSizeFromBottomForPhones() {
+        initializeVarsForPhone()
+        val dp = newDP()
+        val previewRect = Rect(0, 0, 100, 100)
+        val canvasWidth = dp.widthPx / 2
+        val canvasHeight = dp.heightPx / 2
+        val currentRotation = 0
+        val isRtl = false
+
+        mPreviewPositionHelper.updateThumbnailMatrix(previewRect, mThumbnailData, canvasWidth,
+                canvasHeight, dp, currentRotation, isRtl)
+
+        val expectedClippedInsets = RectF(0f, 0f, 0f, 0f)
+        assertThat(mPreviewPositionHelper.getInsetsToDrawInFullscreen(dp))
+                .isEqualTo(expectedClippedInsets)
+    }
+}
\ No newline at end of file
diff --git a/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java b/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
index 7d414f4..190b002 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;
 
@@ -36,6 +34,7 @@
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.Info;
 import com.android.launcher3.util.LauncherModelHelper;
+import com.android.launcher3.util.NavigationMode;
 import com.android.launcher3.util.ReflectionHelpers;
 import com.android.launcher3.util.RotationUtils;
 import com.android.launcher3.util.WindowBounds;
@@ -148,7 +147,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 +163,16 @@
                 }
 
                 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());
+                doReturn(NavigationMode.NO_BUTTON).when(wmProxy).getNavigationMode(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/ic_all_apps_bg_hand.xml b/res/drawable/ic_all_apps_bg_hand.xml
deleted file mode 100644
index 7f3fe14..0000000
--- a/res/drawable/ic_all_apps_bg_hand.xml
+++ /dev/null
@@ -1,100 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 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.
--->
-
-<vector
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="208dp"
-    android:height="212dp"
-    android:viewportWidth="208.0"
-    android:viewportHeight="212.0">
-    <path
-        android:fillColor="#1A000000"
-        android:pathData="M89.4,61.8H85l-1.6-1.5c5.5-6.4,8.8-14.7,8.8-23.7C92.2,16.6,76,0.3,55.9,0.3
-        S19.5,16.6,19.5,36.6S35.8,73,55.9,73c9,0,17.3-3.3,23.7-8.8l1.5,1.6v4.4l40.5,40.4l8.3-8.3L89.4,61.8z M54,66.6
-        c-13.9,0-28.8-16-28.8-30S41.5,8.9,55.4,8.9S81,22.7,81,36.6S67.9,66.6,54,66.6z"/>
-    <path
-        android:fillColor="#1A000000"
-        android:pathData="M33.4,29.2l-0.3-1.8l-4.2-3.1L18.1,26l-3.1,4.2l0.3,1.8L4.5,33.7L9,62.5
-        c0.2,1.5,1.6,2.5,3.1,2.3l34.2-5.3c1.5-0.2,2.5-1.6,2.3-3.1l-4.4-28.8L33.4,29.2z"/>
-    <path
-        android:fillColor="#3367D6"
-        android:pathData="M30.2,27.9l-0.3-1.8l-4.1-3L15,24.7l-3,4.1l0.3,1.8L1.6,32.3L6,60.9
-        c0.2,1.5,1.6,2.5,3.1,2.3L43,57.9c1.5-0.2,2.5-1.6,2.3-3.1l-4.4-28.6L30.2,27.9z M26.6,28.4l-10.7,1.6l-0.3-1.8l10.7-1.6L26.6,28.4z"/>
-    <group>
-        <clip-path
-            android:pathData="M25.1,37.7a28.9,28.9 0 1,0 57.8,0a28.9,28.9 0 1,0 -57.8,0"/>
-        <path
-            android:fillColor="#4285F4"
-            android:pathData="M41.7,23l-0.3-2.1l-4.9-3.6l-12.6,1.9l-3.6,4.9l0.3,2.1L8.1,28.1l5.2,33.7
-                c0.3,1.7,1.9,2.9,3.6,2.7l40-6.1c1.7-0.3,2.9-1.9,2.7-3.6L54.4,21L41.7,23z M37.5,23.6l-12.6,1.9l-0.3-2.1l12.6-1.9L37.5,23.6z"/>
-    </group>
-    <path
-        android:fillColor="?android:attr/colorControlHighlight"
-        android:pathData="M87.5,62.9h-4.4l-1.6-1.5c5.5-6.4,8.8-14.7,8.8-23.7C90.3,17.7,74,1.4,54,1.4
-        S17.6,17.7,17.6,37.7S33.9,74.1,54,74.1c9,0,17.3-3.3,23.7-8.8l1.5,1.6v4.4l40.5,40.4l8.3-8.3L87.5,62.9z M54,64.2
-        c-14.7,0-26.5-11.8-26.5-26.5S39.3,11.2,54,11.2s26.5,11.8,26.5,26.5S68.6,64.2,54,64.2z"/>
-    <path
-        android:fillColor="#1A000000"
-        android:pathData="M153.4,112.9c-14.9-17.2-38.6-9.1-38.6-9.1l-10.2-11.3c0,0-4.8-5.9-9-3.7
-        c-7,3.6-0.6,10.7-0.6,10.7s12.3,15.1,15.4,20.1c2.1,3.4,8.4,4.5,10.1,4.9l17.1,3.7l-0.9-0.7l-1-0.7L153.4,112.9z"/>
-    <path
-        android:fillColor="#FFDBA6"
-        android:pathData="M152.1,113.9c-14.9-17.2-37.6-8-37.6-8l-11.1-12.3c0,0-4.8-5.9-9-3.7
-        c-7,3.6-0.6,10.7-0.6,10.7s12.3,15.1,15.4,20.1c2.1,3.4,8.4,4.5,10.1,4.9l19,4.1"/>
-    <path
-        android:fillColor="#1A000000"
-        android:pathData="M148.6,77.9c0.6,0.7,2,2.5,2.1,2.6c1.1,1.7,6.2,13.6,11.8,35.1c0,0.1,1.9,3,1.9,3.1
-        c0,0,0.1,0.1,0.1,0.2c0,0,0,0,0-0.1c0.9,1.3,4.4,6.6,8.9,13.7c0.1,0.2,0.3,0.5,0.4,0.7c0,0.1,0.1,0.1,0.1,0.2
-        c0.2,0.3,0.4,0.6,0.6,0.9c0.1-0.1,0.2-0.2,0.3-0.3c0.2-0.3,0.6-0.3,0.8,0c2.9,4.8,21.2,35,26.7,49c2.1,5.3,3.2,8.4,3.6,11.6
-        c0.3,2.3,0,4.4-1.2,6c1.5-1.9,3.5-6.8-1.5-19c-1.2-2.9-2.8-6.5-4.8-10.5c-7.5-15.2-20-35.6-22.4-39.6c-0.2-0.3-0.6-0.3-0.8,0
-        c-0.2,0.2-0.3,0.4-0.5,0.6c-4.5-7.1-8.2-12.6-8.8-13.5c-0.1-0.1-1.9-3-1.9-3.1c-5.7-21.6-10.7-33.4-11.8-35.1
-        c-0.1-0.1-1.5-1.9-2.1-2.6l-6.5-8.3c-1.9-2.3-4.2-4.1-6.7-2.3c-2.5,1.8-1.6,4.5-0.1,7.1l3.3,5.2l7-2
-        C147.7,77.4,148.1,77.3,148.6,77.9z"/>
-    <path
-        android:fillColor="#FFDBA6"
-        android:pathData="M148.6,77.9l-6.5-8.3c-1.9-2.3-4.2-4.1-6.7-2.3l0,0l0,0c-2.5,1.8-1.6,4.5-0.1,7.1l3.8,6L148.6,77.9C148.6,77.9,148.6,77.9,148.6,77.9z"/>
-    <path
-        android:fillColor="#1A000000"
-        android:pathData="M151.1,92.5l-19.7-25.3c-1.9-2.3-4.2-4.1-6.7-2.3l0,0l0,0c-2.5,1.8-1.6,4.5-0.1,7.1l17.1,27.2L151.1,92.5z"/>
-    <path
-        android:fillColor="#FFDBA6"
-        android:pathData="M149.7,92.9l-19.7-25.3c-1.9-2.3-4.2-4.1-6.7-2.3l0,0l0,0c-2.5,1.8-1.6,4.5-0.1,7.1l17.1,27.2L149.7,92.9z"/>
-    <path
-        android:fillColor="#1A000000"
-        android:pathData="M141.6,94.6l-20.8-26.7c-2.1-2.5-4.4-4.3-7.1-2.5l0,0l0,0c-2.6,1.9-1.7,4.7-0.1,7.5l18,28.6L141.6,94.6z"/>
-    <path
-        android:fillColor="#FFDBA6"
-        android:pathData="M140.1,95l-20.8-26.7c-2.1-2.5-4.4-4.3-7.1-2.5l0,0h0c-2.6,1.9-1.7,4.7-0.1,7.5l18,28.6L140.1,95z"/>
-    <path
-        android:fillColor="#1A000000"
-        android:pathData="M140.4,99.1c-0.5-0.6-2.1-7.5-2.8-7.3l-15.9-6.9c-0.3,0-0.5-0.1-0.7-0.3l-2.3-3.5
-        l-0.4-0.6L100,54.5c-1.5-2-3.3-4-5.3-4.3c-0.7-0.1-1.3-0.2-2,0.2v0c-1,0.6-1.2,1.5-1.3,2.4c-0.2,1.8,0.6,3.9,1.6,5.9L108.5,87
-        l0.2,0.4l6.6,11.7l0,0c2.5,4.5,4.4,10.5,4.4,10.7L140.4,99.1"/>
-    <path
-        android:fillColor="#FFDBA6"
-        android:pathData="M129.7,125.1c3,0.7,8.1,4,11.8,9.1c2.7,3.7,5.5,8.3,8.2,13
-        c7.6-2.3,19.9-6.8,24.9-12.9c-0.2-0.3-0.4-0.6-0.6-0.9c0-0.1-0.1-0.1-0.1-0.2c-0.1-0.2-0.3-0.5-0.4-0.7c-4.5-7.1-8-12.4-8.9-13.7
-        c0,0,0,0,0,0.1c0-0.1-0.1-0.1-0.1-0.2c-0.1-0.1-1.9-3-1.9-3.1c-5.7-21.5-10.7-33.4-11.8-35.1c-0.1-0.1-1.5-1.9-2.1-2.6
-        c-0.5-0.6-0.9-0.5-1.6-0.3l-26.8,7.7c-0.3,0-0.5-0.1-0.7-0.3l-2.3-3.5l-0.4-0.6L98.5,54.8c-1.5-2-3.3-4-5.3-4.3
-        c-0.7-0.1-1.3-0.2-2,0.2c-1,0.6-1.2,1.5-1.3,2.4c-0.2,1.8,0.6,3.9,1.6,5.9L107,87.3l0.2,0.4l6.6,11.7l0,0
-        c2.5,4.5,4.4,10.5,4.4,10.7L129.7,125.1"/>
-    <path
-        android:fillColor="?android:attr/colorForeground"
-        android:pathData="M202.3,183.1c-5.4-14.1-23.8-44.3-26.7-49c-0.2-0.3-0.6-0.3-0.8,0
-        c-5.1,6.6-19,11.4-26.5,13.6c-0.3,0.1-1,0.1-1.2,0.6c-0.2,0.4,0.1,1,0.2,1.1c7.8,12.9,14.7,27.9,15.3,29.3c0,0.1,0.1,0.1,0.1,0.2
-        l9.6,22.9c0,0,0,0,0,0l1.7,4.1c1.4,2.7,3,4.3,5.3,5.1c1.5,0.5,2.1,0.6,3.2,0.6c4.8,0.1,15.2-6.1,20.5-9.4c2.7-1.7,3.3-4.4,2.9-7.6
-        C205.5,191.5,204.4,188.4,202.3,183.1z"/>
-</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_all_apps_bg_icon_1.xml b/res/drawable/ic_all_apps_bg_icon_1.xml
deleted file mode 100644
index d226ac6..0000000
--- a/res/drawable/ic_all_apps_bg_icon_1.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 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.
--->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="48dp"
-        android:height="48dp"
-        android:viewportWidth="48.0"
-        android:viewportHeight="48.0">
-
-    <path
-        android:fillColor="#1A000000"
-        android:pathData="M44.28,30.96c4.84-10.68,0.09-23.27-10.59-28.11S10.42,2.74,5.58,13.42
-        C1,23.54,6.5,35.92,16.62,40.51l0,0l-3.23,7.12C27.84,47,39.79,40.86,44.28,30.96z" />
-    <path
-        android:fillColor="?android:attr/colorPrimary"
-        android:pathData="M41.75,30.05c4.84-10.68,0.09-23.27-10.59-28.11S7.9,1.83,3.06,12.51
-        c-4.59,10.12,0.92,22.5,11.03,27.09l0,0l-3.23,7.12C25.31,46.09,37.26,39.94,41.75,30.05z" />
-</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_all_apps_bg_icon_2.xml b/res/drawable/ic_all_apps_bg_icon_2.xml
deleted file mode 100644
index 5966d99..0000000
--- a/res/drawable/ic_all_apps_bg_icon_2.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 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.
--->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="48dp"
-        android:height="48dp"
-        android:viewportWidth="48.0"
-        android:viewportHeight="48.0">
-
-    <path
-        android:fillColor="#1A000000"
-        android:pathData="M20.54,44.59c0.57-0.04,1.15-0.38,1.67-1.04l24.23-30.62c0.62-0.78,0.77-1.54,0.52-2.12
-        c-0.25-0.58-0.9-0.99-1.89-1.1L6.2,5.99C5.39,5.91,4.74,6.08,4.32,6.44l0,0C3.7,6.97,3.55,7.88,4.01,8.96l14.54,34.09
-        C19,44.13,19.75,44.65,20.54,44.59L20.54,44.59z" />
-    <path
-        android:fillColor="?android:attr/colorPrimary"
-        android:pathData="M18.49,43.22c0.57-0.04,1.15-0.38,1.67-1.04l24.23-30.62c0.62-0.78,0.77-1.54,0.52-2.12
-        c-0.25-0.58-0.9-0.99-1.89-1.1L4.15,4.62C3.34,4.54,2.69,4.71,2.27,5.08l0,0C1.65,5.6,1.5,6.52,1.96,7.6L16.5,41.69
-        C16.96,42.76,17.7,43.28,18.49,43.22L18.49,43.22z" />
-</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_all_apps_bg_icon_3.xml b/res/drawable/ic_all_apps_bg_icon_3.xml
deleted file mode 100644
index b18f8bc..0000000
--- a/res/drawable/ic_all_apps_bg_icon_3.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 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.
--->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="48dp"
-        android:height="48dp"
-        android:viewportWidth="48.0"
-        android:viewportHeight="48.0">
-
-    <path
-        android:fillColor="#1A000000"
-        android:pathData="M25.18,1.27c-12.32,0-23.41,9.99-23.41,22.31s11.09,22.31,23.41,22.31
-        s22.31-9.99,22.31-22.31S37.5,1.27,25.18,1.27z M25.18,33.55c-5.5,0-14.35-5.1-14.35-10.6s8.32-12.19,13.82-12.19
-        c5.5,0,10.49,7.33,10.49,12.83S30.68,33.55,25.18,33.55z" />
-    <path
-        android:fillColor="?android:attr/colorPrimary"
-        android:pathData="M22.93,0.22c-12.32,0-22.31,9.99-22.31,22.31s9.99,22.31,22.31,22.31
-        s22.31-9.99,22.31-22.31S35.25,0.22,22.93,0.22z M22.93,32.5c-5.5,0-9.97-4.46-9.97-9.97s4.46-9.97,9.97-9.97
-        c5.5,0,9.97,4.46,9.97,9.97S28.43,32.5,22.93,32.5z" />
-    <path
-        android:fillColor="?android:attr/colorPrimary"
-        android:pathData="M14.81,22.53a8.12,8.12 0 1,0 16.24,0a8.12,8.12 0 1,0 -16.24,0z" />
-</vector>
diff --git a/res/drawable/ic_all_apps_bg_icon_4.xml b/res/drawable/ic_all_apps_bg_icon_4.xml
deleted file mode 100644
index 8eb4d90..0000000
--- a/res/drawable/ic_all_apps_bg_icon_4.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 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.
--->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="48dp"
-        android:height="48dp"
-        android:viewportWidth="48.0"
-        android:viewportHeight="48.0">
-
-    <path
-        android:fillColor="#1A000000"
-        android:pathData="M11.53,8.02l23.39-5.73c1.61-0.39,3.25,0.6,3.64,2.21l7.64,31.19
-        c0.39,1.61-0.6,3.25-2.21,3.64L12.8,46.97c-1.61,0.39-3.25-0.6-3.64-2.21L3.43,21.37L11.53,8.02z" />
-    <path
-        android:fillColor="?android:attr/colorPrimary"
-        android:pathData="M9.2,6.53L32.59,0.8C34.2,0.4,35.84,1.4,36.23,3l7.64,31.19c0.39,1.61-0.6,3.25-2.21,3.64
-        l-31.19,7.64c-1.61,0.39-3.25-0.6-3.64-2.21L1.11,19.87L9.2,6.53z" />
-    <path
-        android:fillColor="#1A000000"
-        android:pathData="M9.27,6.47l1.91,7.8c0.4,1.62-0.59,3.24-2.21,3.64l-7.8,1.91L9.27,6.47z" />
-</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_block_shadow.xml b/res/drawable/ic_block_shadow.xml
deleted file mode 100644
index 045fe8d..0000000
--- a/res/drawable/ic_block_shadow.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 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.launcher3.graphics.ShadowDrawable
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:src="@drawable/ic_block_no_shadow"
-    android:elevation="@dimen/drop_target_shadow_elevation" />
diff --git a/res/drawable/ic_remove_shadow.xml b/res/drawable/ic_remove_shadow.xml
deleted file mode 100644
index 48abc10..0000000
--- a/res/drawable/ic_remove_shadow.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 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.launcher3.graphics.ShadowDrawable
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:src="@drawable/ic_remove_no_shadow"
-    android:elevation="@dimen/drop_target_shadow_elevation" />
diff --git a/res/drawable/ic_setup_shadow.xml b/res/drawable/ic_setup_shadow.xml
deleted file mode 100644
index 10aeee6..0000000
--- a/res/drawable/ic_setup_shadow.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 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.launcher3.graphics.ShadowDrawable
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:src="@drawable/ic_setting"
-    android:elevation="@dimen/drop_target_shadow_elevation" />
diff --git a/res/drawable/ic_uninstall_shadow.xml b/res/drawable/ic_uninstall_shadow.xml
deleted file mode 100644
index b441b0e..0000000
--- a/res/drawable/ic_uninstall_shadow.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 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.launcher3.graphics.ShadowDrawable
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:src="@drawable/ic_uninstall_no_shadow"
-    android:elevation="@dimen/drop_target_shadow_elevation" />
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/all_apps_bottom_sheet_background.xml b/res/layout/all_apps_bottom_sheet_background.xml
index 12b6b7b..3e47690 100644
--- a/res/layout/all_apps_bottom_sheet_background.xml
+++ b/res/layout/all_apps_bottom_sheet_background.xml
@@ -22,7 +22,7 @@
     <View
         android:id="@+id/bottom_sheet_handle_area"
         android:layout_width="match_parent"
-        android:layout_height="36dp" />
+        android:layout_height="@dimen/bottom_sheet_handle_area_height" />
 
     <View
         android:id="@+id/bottom_sheet_handle"
diff --git a/res/layout/all_apps_personal_work_tabs.xml b/res/layout/all_apps_personal_work_tabs.xml
index 11143fb..4459c87 100644
--- a/res/layout/all_apps_personal_work_tabs.xml
+++ b/res/layout/all_apps_personal_work_tabs.xml
@@ -20,6 +20,9 @@
     android:layout_width="match_parent"
     android:layout_height="@dimen/all_apps_header_pill_height"
     android:layout_gravity="center_horizontal"
+    android:paddingTop="@dimen/all_apps_tabs_vertical_padding"
+    android:paddingBottom="@dimen/all_apps_tabs_vertical_padding"
+    android:layout_marginTop="@dimen/all_apps_tabs_margin_top"
     android:orientation="horizontal"
     style="@style/TextHeadline">
 
@@ -28,7 +31,6 @@
         android:layout_width="0dp"
         android:layout_height="match_parent"
         android:layout_marginEnd="@dimen/all_apps_tabs_button_horizontal_padding"
-        android:layout_marginVertical="@dimen/all_apps_tabs_vertical_padding"
         android:layout_weight="1"
         android:background="@drawable/all_apps_tabs_background"
         android:text="@string/all_apps_personal_tab"
@@ -41,7 +43,6 @@
         android:layout_width="0dp"
         android:layout_height="match_parent"
         android:layout_marginStart="@dimen/all_apps_tabs_button_horizontal_padding"
-        android:layout_marginVertical="@dimen/all_apps_tabs_vertical_padding"
         android:layout_weight="1"
         android:background="@drawable/all_apps_tabs_background"
         android:text="@string/all_apps_work_tab"
diff --git a/res/layout/all_apps_search_market.xml b/res/layout/all_apps_search_market.xml
deleted file mode 100644
index 6f2dd3d..0000000
--- a/res/layout/all_apps_search_market.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<TextView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/search_market_text"
-    android:layout_width="match_parent"
-    android:layout_height="48dp"
-    android:gravity="center"
-    android:paddingLeft="@dimen/dynamic_grid_edge_margin"
-    android:paddingRight="@dimen/dynamic_grid_edge_margin"
-    android:fontFamily="sans-serif-medium"
-    android:textSize="14sp"
-    android:textColor="?android:attr/colorAccent"
-    android:text="@string/all_apps_search_market_message"
-    android:textAllCaps="true"
-    android:focusable="true"
-    android:background="?android:selectableItemBackground" />
diff --git a/res/layout/hotseat.xml b/res/layout/hotseat.xml
index 82b0b8d..95ebd94 100644
--- a/res/layout/hotseat.xml
+++ b/res/layout/hotseat.xml
@@ -21,4 +21,5 @@
     android:layout_height="match_parent"
     android:theme="@style/HomeScreenElementTheme"
     android:importantForAccessibility="no"
+    android:preferKeepClear="true"
     launcher:containerType="hotseat" />
\ No newline at end of file
diff --git a/res/layout/search_container_all_apps.xml b/res/layout/search_container_all_apps.xml
index e1646ba..b46298c 100644
--- a/res/layout/search_container_all_apps.xml
+++ b/res/layout/search_container_all_apps.xml
@@ -26,6 +26,7 @@
     android:gravity="center"
     android:hint="@string/all_apps_search_bar_hint"
     android:imeOptions="actionSearch|flagNoExtractUi"
+    android:importantForAutofill="no"
     android:inputType="text|textNoSuggestions|textCapWords"
     android:maxLines="1"
     android:padding="8dp"
diff --git a/res/layout/search_results_rv_layout.xml b/res/layout/search_results_rv_layout.xml
index 567cb5f..9127521 100644
--- a/res/layout/search_results_rv_layout.xml
+++ b/res/layout/search_results_rv_layout.xml
@@ -18,7 +18,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/search_results_list_view"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
+    android:layout_height="match_parent"
     android:clipToPadding="false"
     android:descendantFocusability="afterDescendants"
     android:focusable="true" />
diff --git a/res/layout/secondary_launcher.xml b/res/layout/secondary_launcher.xml
index 635db14..4be2e45 100644
--- a/res/layout/secondary_launcher.xml
+++ b/res/layout/secondary_launcher.xml
@@ -18,6 +18,7 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:id="@+id/drag_layer"
+    android:clipChildren="false"
     android:padding="@dimen/dynamic_grid_edge_margin">
 
     <GridView
@@ -52,7 +53,6 @@
         android:saveEnabled="false"
         android:layout_gravity="bottom|end"
         android:background="@drawable/round_rect_primary"
-        android:elevation="2dp"
         android:visibility="invisible" >
 
         <include
@@ -76,35 +76,8 @@
             android:paddingTop="@dimen/all_apps_header_top_padding"
             android:orientation="vertical" >
 
-            <com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip
-                android:id="@+id/tabs"
-                android:layout_width="match_parent"
-                android:layout_height="@dimen/all_apps_header_pill_height"
-                android:orientation="horizontal"
-                style="@style/TextHeadline">
-
-                <Button
-                    android:id="@+id/tab_personal"
-                    android:layout_width="0dp"
-                    android:layout_height="match_parent"
-                    android:layout_weight="1"
-                    android:background="?android:attr/selectableItemBackground"
-                    android:text="@string/all_apps_personal_tab"
-                    android:textAllCaps="true"
-                    android:textColor="@color/all_apps_tab_text"
-                    android:textSize="14sp" />
-
-                <Button
-                    android:id="@+id/tab_work"
-                    android:layout_width="0dp"
-                    android:layout_height="match_parent"
-                    android:layout_weight="1"
-                    android:background="?android:attr/selectableItemBackground"
-                    android:text="@string/all_apps_work_tab"
-                    android:textAllCaps="true"
-                    android:textColor="@color/all_apps_tab_text"
-                    android:textSize="14sp" />
-            </com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip>
+            <include layout="@layout/floating_header_content" />
+            <include layout="@layout/all_apps_personal_work_tabs" />
         </com.android.launcher3.allapps.FloatingHeaderView>
 
         <com.android.launcher3.allapps.search.AppsSearchContainerLayout
@@ -113,12 +86,14 @@
             android:layout_height="@dimen/all_apps_search_bar_field_height"
             android:layout_centerHorizontal="true"
             android:layout_gravity="top|center_horizontal"
+            android:layout_marginTop="24dp"
             android:background="@drawable/bg_all_apps_searchbox"
             android:elevation="1dp"
             android:focusableInTouchMode="true"
             android:gravity="center"
             android:hint="@string/all_apps_search_bar_hint"
             android:imeOptions="actionSearch|flagNoExtractUi"
+            android:importantForAutofill="no"
             android:inputType="text|textNoSuggestions|textCapWords"
             android:maxLines="1"
             android:padding="8dp"
diff --git a/res/layout/user_folder_icon_normalized.xml b/res/layout/user_folder_icon_normalized.xml
index 11eea60..4dee6e7 100644
--- a/res/layout/user_folder_icon_normalized.xml
+++ b/res/layout/user_folder_icon_normalized.xml
@@ -30,11 +30,12 @@
     <LinearLayout
         android:id="@+id/folder_footer"
         android:layout_width="match_parent"
-        android:layout_height="48dp"
+        android:layout_height="@dimen/folder_label_height"
         android:clipChildren="false"
         android:orientation="horizontal"
         android:paddingLeft="12dp"
-        android:paddingRight="12dp" >
+        android:paddingRight="12dp"
+        android:alpha="0">
 
         <com.android.launcher3.folder.FolderNameEditText
             android:id="@+id/folder_name"
@@ -47,6 +48,7 @@
             android:gravity="center_horizontal"
             android:hint="@string/folder_hint_text"
             android:imeOptions="flagNoExtractUi"
+            android:importantForAutofill="no"
             android:singleLine="true"
             android:textColor="?attr/folderTextColor"
             android:textColorHighlight="?android:attr/colorControlHighlight"
diff --git a/res/layout/widgets_full_sheet_paged_view.xml b/res/layout/widgets_full_sheet_paged_view.xml
index dfe226a..098c9b0 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
+    <com.android.launcher3.views.StickyHeaderLayout
         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,7 +67,8 @@
             android:elevation="0.1dp"
             android:background="?android:attr/colorBackground"
             android:paddingBottom="8dp"
-            android:clipToPadding="false">
+            android:paddingHorizontal="@dimen/widget_list_horizontal_margin"
+            launcher:layout_sticky="true">
             <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
@@ -91,7 +92,8 @@
             android:paddingLeft="@dimen/widget_tabs_horizontal_padding"
             android:paddingRight="@dimen/widget_tabs_horizontal_padding"
             android:background="?android:attr/colorBackground"
-            style="@style/TextHeadline">
+            style="@style/TextHeadline"
+            launcher:layout_sticky="true">
 
             <Button
                 android:id="@+id/tab_personal"
@@ -120,5 +122,5 @@
                 style="?android:attr/borderlessButtonStyle" />
         </com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip>
 
-    </com.android.launcher3.widget.picker.SearchAndRecommendationsView>
+    </com.android.launcher3.views.StickyHeaderLayout>
 </merge>
\ No newline at end of file
diff --git a/res/layout/widgets_full_sheet_recyclerview.xml b/res/layout/widgets_full_sheet_recyclerview.xml
index 6a5d6cb..9da3e87 100644
--- a/res/layout/widgets_full_sheet_recyclerview.xml
+++ b/res/layout/widgets_full_sheet_recyclerview.xml
@@ -13,21 +13,21 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto" >
     <com.android.launcher3.widget.picker.WidgetsRecyclerView
         android:id="@+id/primary_widgets_list_view"
         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 -->
-    <com.android.launcher3.widget.picker.SearchAndRecommendationsView
+    <com.android.launcher3.views.StickyHeaderLayout
         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 +40,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,8 +49,10 @@
             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">
+            android:clipToPadding="false"
+            launcher:layout_sticky="true" >
             <include layout="@layout/widgets_search_bar" />
         </FrameLayout>
 
@@ -60,7 +63,8 @@
             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>
+    </com.android.launcher3.views.StickyHeaderLayout>
 
 </merge>
\ No newline at end of file
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/widgets_search_bar.xml b/res/layout/widgets_search_bar.xml
index 9178a75..6d44865 100644
--- a/res/layout/widgets_search_bar.xml
+++ b/res/layout/widgets_search_bar.xml
@@ -23,6 +23,7 @@
         android:layout_weight="1"
         android:inputType="text"
         android:imeOptions="actionSearch"
+        android:importantForAutofill="no"
         android:textColor="?android:attr/textColorPrimary"
         android:textColorHint="?android:attr/textColorSecondary"/>
 
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/layout/work_mode_fab.xml b/res/layout/work_mode_fab.xml
index d2fa5fa..81b28ba 100644
--- a/res/layout/work_mode_fab.xml
+++ b/res/layout/work_mode_fab.xml
@@ -26,6 +26,7 @@
     android:textColor="@color/all_apps_tab_text"
     android:textSize="14sp"
     android:background="@drawable/work_apps_toggle_background"
+    android:forceHasOverlappingRendering="false"
     android:drawablePadding="8dp"
     android:drawableStart="@drawable/ic_corp_off"
     android:layout_marginBottom="@dimen/work_fab_margin_bottom"
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 89b62ee..500fce2 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Deursoek programme"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Laai tans programme …"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Kon geen programme kry wat by \"<xliff:g id="QUERY">%1$s</xliff:g>\" pas nie"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Soek meer programme"</string>
     <string name="label_application" msgid="8531721983832654978">"Program"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Alle programme"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Kennisgewings"</string>
@@ -137,7 +136,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 ec69acb..b7d4a7d 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"መተግበሪያዎችን ፈልግ"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"መተግበሪያዎችን በመጫን ላይ…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"ከ«<xliff:g id="QUERY">%1$s</xliff:g>» ጋር የሚዛመዱ ምንም መተግበሪያዎች አልተገኙም"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"ተጨማሪ መተግበሪያዎች ይፈልጉ"</string>
     <string name="label_application" msgid="8531721983832654978">"መተግበሪያ"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"ሁሉም መተግበሪያዎች"</string>
     <string name="notifications_header" msgid="1404149926117359025">"ማሳወቂያዎች"</string>
@@ -137,7 +136,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 fe076c9..c234900 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"بحث في التطبيقات"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"جارٍ تحميل التطبيقات…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"لم يتم العثور على أي تطبيقات تتطابق مع \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"البحث عن مزيد من التطبيقات"</string>
     <string name="label_application" msgid="8531721983832654978">"تطبيق"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"جميع التطبيقات"</string>
     <string name="notifications_header" msgid="1404149926117359025">"الإشعارات"</string>
@@ -137,7 +136,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 0add9ae..eee5ae2 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"এপসমূহ সন্ধান কৰক"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"এপসমূহ ল’ড কৰি থকা হৈছে…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\"ৰ সৈতে মিলা কোনো এপ্ বিচাৰি পোৱা নগ\'ল"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"আৰু অধিক এপবোৰ সন্ধান কৰক"</string>
     <string name="label_application" msgid="8531721983832654978">"এপ্"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"আটাইবোৰ এপ্"</string>
     <string name="notifications_header" msgid="1404149926117359025">"জাননীসমূহ"</string>
@@ -137,7 +136,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..2e07405 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Tətbiqləri axtarın"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Tətbiqlər yüklənir…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"<xliff:g id="QUERY">%1$s</xliff:g> sorğusuna uyğun tətbiq tapılmadı"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Daha çox tətbiq üçün axtarış edin"</string>
     <string name="label_application" msgid="8531721983832654978">"Tətbiq"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Bütün tətbiqlər"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Bildirişlər"</string>
@@ -137,7 +136,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..cc6e908 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Pretražite aplikacije"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Aplikacije se učitavaju…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Nije pronađena nijedna aplikacija za „<xliff:g id="QUERY">%1$s</xliff:g>“"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Pretraži još aplikacija"</string>
     <string name="label_application" msgid="8531721983832654978">"Aplikacija"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Sve aplikacije"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Obaveštenja"</string>
@@ -137,7 +136,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..69a16ba 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Пошук праграм"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Праграмы загружаюцца…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Праграм, якія адпавядаюць запыту \"<xliff:g id="QUERY">%1$s</xliff:g>\", не знойдзена"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Шукаць іншыя праграмы"</string>
     <string name="label_application" msgid="8531721983832654978">"Праграма"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Усе праграмы"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Апавяшчэнні"</string>
@@ -137,7 +136,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..549c8ea 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Търсене в приложенията"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Приложенията се зареждат…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Няма намерени приложения, съответстващи на „<xliff:g id="QUERY">%1$s</xliff:g>“"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Търсене на още приложения"</string>
     <string name="label_application" msgid="8531721983832654978">"Приложение"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Всички приложения"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Известия"</string>
@@ -137,7 +136,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..9683182 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"অ্যাপ খুঁজুন"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"অ্যাপ লোড হচ্ছে…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" এর সাথে মেলে এমন কোনো অ্যাপ পাওয়া যায়নি"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"আরও অ্যাপ্লিকেশানের জন্য খুঁজুন"</string>
     <string name="label_application" msgid="8531721983832654978">"অ্যাপ"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"সব অ্যাপ"</string>
     <string name="notifications_header" msgid="1404149926117359025">"বিজ্ঞপ্তি"</string>
@@ -137,7 +136,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..f99525f 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Pretražite aplikacije"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Aplikacije se učitavaju…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Nije pronađena nijedna aplikacija za upit \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Pretraži više aplikacija"</string>
     <string name="label_application" msgid="8531721983832654978">"Aplikacija"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Sve aplikacije"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Obavještenja"</string>
@@ -137,7 +136,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..d8ede15 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Cerca aplicacions"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"S\'estan carregant les aplicacions…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"No s\'ha trobat cap aplicació que coincideixi amb \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Cerca més aplicacions"</string>
     <string name="label_application" msgid="8531721983832654978">"Aplicació"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Totes les aplicacions"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Notificacions"</string>
@@ -137,7 +136,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 5c357f7..600d66f 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Hledat v aplikacích"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Načítání aplikací…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Dotazu „<xliff:g id="QUERY">%1$s</xliff:g>“ neodpovídají žádné aplikace"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Vyhledat další aplikace"</string>
     <string name="label_application" msgid="8531721983832654978">"Aplikace"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Všechny aplikace"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Oznámení"</string>
@@ -137,7 +136,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..4da7b84 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Søg efter apps"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Indlæser apps…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Der blev ikke fundet nogen apps, som matcher \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Søg efter flere apps"</string>
     <string name="label_application" msgid="8531721983832654978">"App"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Alle apps"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Notifikationer"</string>
@@ -137,7 +136,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..3c2514f 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Apps finden"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Apps werden geladen…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Keine Apps für \"<xliff:g id="QUERY">%1$s</xliff:g>\" gefunden"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Weitere Apps suchen"</string>
     <string name="label_application" msgid="8531721983832654978">"App"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Alle Apps"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Benachrichtigungen"</string>
@@ -137,7 +136,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..ae9a8fd 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Αναζήτηση εφαρμογών"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Φόρτωση εφαρμογών…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Δεν βρέθηκαν εφαρμογές αντιστοίχισης για \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Αναζήτηση περισσότερων εφαρμογών"</string>
     <string name="label_application" msgid="8531721983832654978">"Εφαρμογή"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Όλες οι εφαρμογές"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Ειδοποιήσεις"</string>
@@ -137,7 +136,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..4f15eb5 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Search apps"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Loading apps…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"No apps found matching \'<xliff:g id="QUERY">%1$s</xliff:g>\'"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Search for more apps"</string>
     <string name="label_application" msgid="8531721983832654978">"App"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"All apps"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Notifications"</string>
@@ -137,7 +136,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..4f15eb5 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Search apps"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Loading apps…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"No apps found matching \'<xliff:g id="QUERY">%1$s</xliff:g>\'"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Search for more apps"</string>
     <string name="label_application" msgid="8531721983832654978">"App"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"All apps"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Notifications"</string>
@@ -137,7 +136,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..4f15eb5 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Search apps"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Loading apps…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"No apps found matching \'<xliff:g id="QUERY">%1$s</xliff:g>\'"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Search for more apps"</string>
     <string name="label_application" msgid="8531721983832654978">"App"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"All apps"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Notifications"</string>
@@ -137,7 +136,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..4f15eb5 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Search apps"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Loading apps…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"No apps found matching \'<xliff:g id="QUERY">%1$s</xliff:g>\'"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Search for more apps"</string>
     <string name="label_application" msgid="8531721983832654978">"App"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"All apps"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Notifications"</string>
@@ -137,7 +136,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..e644c23 100644
--- a/res/values-en-rXC/strings.xml
+++ b/res/values-en-rXC/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‎‏‏‎‎‏‏‎‏‎‎‏‏‎‎‎‎‏‏‏‏‎‎‏‎‏‏‎‏‎‏‎‎‏‏‏‏‎‎‏‎‎‎‏‏‎‎‏‏‏‎‏‎‎‎‎‏‎‎‏‏‏‎‎Search apps‎‏‎‎‏‎"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‎‏‎‏‏‏‏‎‏‎‏‏‏‎‏‎‎‏‎‎‎‎‏‏‎‏‎‏‎‏‏‏‎‏‏‏‏‏‎‎‎‏‎‏‏‏‏‏‏‎‏‎‏‎‎‎Loading apps…‎‏‎‎‏‎"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‏‏‎‎‎‏‏‎‏‎‎‏‏‏‏‎‏‎‏‎‏‎‎‎‎‎‏‏‏‏‏‎‎‎‏‏‎‎‏‎‏‎‏‏‏‏‎‏‎‏‎‎‎‎‎‎‏‎‏‎‎No apps found matching \"‎‏‎‎‏‏‎<xliff:g id="QUERY">%1$s</xliff:g>‎‏‎‎‏‏‏‎\"‎‏‎‎‏‎"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‎‏‏‎‎‏‎‏‏‏‏‎‏‎‏‏‏‏‏‎‎‎‏‎‏‎‎‏‏‏‏‏‎‎‏‏‎‏‏‏‏‏‎‎‏‎‏‏‏‏‎‎‏‏‎‎‏‏‎‏‎‎‎‎Search for more apps‎‏‎‎‏‎"</string>
     <string name="label_application" msgid="8531721983832654978">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‎‎‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‎‎‎‏‏‏‏‏‎‎‎‏‎‏‏‏‏‎‎‏‎‎‎‎‏‎‎‏‎‎‎‎‎‏‎‎App‎‏‎‎‏‎"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎‏‏‎‎‏‏‎‏‏‏‎‏‎‎‏‎‏‏‎‏‏‏‏‏‎‎‎‏‏‏‏‏‏‎‎‎‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‎‎‏‏‏‎All apps‎‏‎‎‏‎"</string>
     <string name="notifications_header" msgid="1404149926117359025">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‎‏‏‎‎‏‏‎‏‏‏‏‏‎‎‏‎‎‎‏‎‏‎‏‏‏‎‏‎‏‏‎‎‎‏‎‏‎‏‏‎‎‏‏‏‎‏‏‎‏‏‏‏‎‏‏‎‏‏‎‎‎‏‎Notifications‎‏‎‎‏‎"</string>
@@ -137,7 +136,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..0bc8f4f 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Buscar apps"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Cargando apps…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"No hay apps que coincidan con \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Buscar más apps"</string>
     <string name="label_application" msgid="8531721983832654978">"App"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Todas las apps"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Notificaciones"</string>
@@ -137,7 +136,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..4045ad3 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Buscar aplicaciones"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Cargando aplicaciones…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"No se han encontrado aplicaciones que contengan \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Buscar más aplicaciones"</string>
     <string name="label_application" msgid="8531721983832654978">"Aplicación"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Todas las aplicaciones"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Notificaciones"</string>
@@ -137,7 +136,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..effd140 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Otsige rakendusi"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Rakenduste laadimine …"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Päringule „<xliff:g id="QUERY">%1$s</xliff:g>” ei vastanud ükski rakendus"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Otsi rohkem rakendusi"</string>
     <string name="label_application" msgid="8531721983832654978">"Rakendus"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Kõik rakendused"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Märguanded"</string>
@@ -137,7 +136,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..9e2d9a4 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Bilatu aplikazioetan"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Aplikazioak kargatzen…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Ez da aurkitu \"<xliff:g id="QUERY">%1$s</xliff:g>\" bilaketaren emaitzarik"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Bilatu aplikazio gehiago"</string>
     <string name="label_application" msgid="8531721983832654978">"Aplikazioa"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Aplikazio guztiak"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Jakinarazpenak"</string>
@@ -137,7 +136,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..a1acda0 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"جستجوی برنامه‌ها"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"درحال بارگیری برنامه‌‌ها…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"هیچ برنامه‌ای در مطابقت با «<xliff:g id="QUERY">%1$s</xliff:g>» پیدا نشد"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"جستجوی برنامه‌های بیشتر"</string>
     <string name="label_application" msgid="8531721983832654978">"برنامه"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"همه برنامه‌ها"</string>
     <string name="notifications_header" msgid="1404149926117359025">"اعلان‌ها"</string>
@@ -137,7 +136,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..89e4106 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Hae sovelluksia"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Ladataan sovelluksia…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"<xliff:g id="QUERY">%1$s</xliff:g> ei palauttanut sovelluksia."</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Hae lisää sovelluksia"</string>
     <string name="label_application" msgid="8531721983832654978">"Sovellus"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Kaikki sovellukset"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Ilmoitukset"</string>
@@ -137,7 +136,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..448569d 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Rechercher dans les applications"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Chargement des applications en cours…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Aucune application trouvée correspondant à « <xliff:g id="QUERY">%1$s</xliff:g> »"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Rechercher plus d\'applications"</string>
     <string name="label_application" msgid="8531721983832654978">"Application"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Toutes les applications"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Notifications"</string>
@@ -137,7 +136,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 dc1129f..b371067 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Rechercher dans les applications"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Chargement des applications…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Aucune application ne correspond à la requête \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Rechercher plus d\'applications"</string>
     <string name="label_application" msgid="8531721983832654978">"Application"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Toutes les applis"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Notifications"</string>
@@ -137,7 +136,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..045d8d7 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Buscar aplicacións"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Cargando aplicacións…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Non se atoparon aplicacións que coincidan con \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Buscar máis aplicacións"</string>
     <string name="label_application" msgid="8531721983832654978">"Aplicación"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Todas as aplicacións"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Notificacións"</string>
@@ -137,7 +136,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..7288570 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"શોધ ઍપ્લિકેશનો"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"ઍપ્લિકેશનો લોડ કરી રહ્યું છે…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\"થી મેળ ખાતી કોઈ ઍપ્લિકેશનો મળી નથી"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"વધુ ઍપ્લિકેશનો શોધો"</string>
     <string name="label_application" msgid="8531721983832654978">"ઍપ"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"બધી ઍપ"</string>
     <string name="notifications_header" msgid="1404149926117359025">"નોટિફિકેશન"</string>
@@ -137,7 +136,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 26c0983..e90e63a 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"ऐप सर्च करें"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"ऐप्लिकेशन लोड हो रहे हैं…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" से मिलता-जुलता कोई ऐप्लिकेशन नहीं मिला"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"और ऐप सर्च करें"</string>
     <string name="label_application" msgid="8531721983832654978">"ऐप्लिकेशन"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"सभी ऐप्लिकेशन"</string>
     <string name="notifications_header" msgid="1404149926117359025">"सूचनाएं"</string>
@@ -137,7 +136,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..a1369e8 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Pretraži aplikacije"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Učitavanje aplikacija…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Nema aplikacija podudarnih s upitom \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Traži više aplikacija"</string>
     <string name="label_application" msgid="8531721983832654978">"Aplikacija"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Sve aplikacije"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Obavijesti"</string>
@@ -137,7 +136,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..a5d4835 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Alkalmazások keresése"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Alkalmazások betöltése…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Nem található alkalmazás a(z) „<xliff:g id="QUERY">%1$s</xliff:g>” lekérdezésre"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"További alkalmazások keresése"</string>
     <string name="label_application" msgid="8531721983832654978">"Alkalmazás"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Összes alkalmazás"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Értesítések"</string>
@@ -137,7 +136,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 af0e168..b8f1643 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Որոնել հավելվածներ"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Հավելվածների բեռնում…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"«<xliff:g id="QUERY">%1$s</xliff:g>» հարցմանը համապատասխանող հավելվածներ չեն գտնվել"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Որոնել այլ հավելվածներ"</string>
     <string name="label_application" msgid="8531721983832654978">"Հավելված"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Բոլոր հավելվածները"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Ծանուցումներ"</string>
@@ -137,7 +136,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..e46026e 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Telusuri aplikasi"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Memuat aplikasi…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Tidak ditemukan aplikasi yang cocok dengan \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Telusuri aplikasi lainnya"</string>
     <string name="label_application" msgid="8531721983832654978">"Aplikasi"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Semua aplikasi"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Notifikasi"</string>
@@ -137,7 +136,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..92be25b 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Leita í forritum"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Hleður forrit…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Ekki fundust forrit sem samsvara „<xliff:g id="QUERY">%1$s</xliff:g>“"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Leita að fleiri forritum"</string>
     <string name="label_application" msgid="8531721983832654978">"Forrit"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Öll forrit"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Tilkynningar"</string>
@@ -137,7 +136,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..b5b7add 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Cerca nelle app"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Caricamento delle app…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Nessuna app trovata corrispondente a \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Cerca altre app"</string>
     <string name="label_application" msgid="8531721983832654978">"App"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Tutte le app"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Notifiche"</string>
@@ -137,7 +136,7 @@
     <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>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Spostati alla riga <xliff:g id="NUMBER_0">%1$s</xliff:g> colonna <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">"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..6b4d129 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"חיפוש אפליקציות"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"טעינת אפליקציות מתבצעת…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"לא נמצאו אפליקציות התואמות ל-\"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"חיפוש אפליקציות נוספות"</string>
     <string name="label_application" msgid="8531721983832654978">"אפליקציה"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"כל האפליקציות"</string>
     <string name="notifications_header" msgid="1404149926117359025">"התראות"</string>
@@ -137,7 +136,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 272183d..1a442a8 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"アプリを検索"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"アプリを読み込んでいます…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"「<xliff:g id="QUERY">%1$s</xliff:g>」に一致するアプリは見つかりませんでした"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"他のアプリを検索"</string>
     <string name="label_application" msgid="8531721983832654978">"アプリ"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"すべてのアプリ"</string>
     <string name="notifications_header" msgid="1404149926117359025">"通知"</string>
@@ -137,7 +136,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..8437dd8 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"აპების ძიება"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"აპები იტვირთება…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"„<xliff:g id="QUERY">%1$s</xliff:g>“-ის თანხვედრი აპები არ მოიძებნა"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"მეტი აპის პოვნა"</string>
     <string name="label_application" msgid="8531721983832654978">"აპი"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"ყველა აპი"</string>
     <string name="notifications_header" msgid="1404149926117359025">"შეტყობინებები"</string>
@@ -137,7 +136,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..ec3ee18 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Қолданбаларды іздеу"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Қолданбалар жүктелуде…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" сұрауына сәйкес келетін қолданбалар жоқ"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Қосымша қолданбалар іздеу"</string>
     <string name="label_application" msgid="8531721983832654978">"Қолданба"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Барлық қолданба"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Хабарландырулар"</string>
@@ -137,7 +136,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..f1c1d08 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"ស្វែងរក​កម្មវិធី"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"កំពុងផ្ទុកកម្មវិធី…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"រកមិនឃើញកម្មវិធី​ដែលត្រូវគ្នាជាមួយ \"<xliff:g id="QUERY">%1$s</xliff:g>\" ទេ"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"ស្វែងរកកម្មវិធីច្រើនទៀត"</string>
     <string name="label_application" msgid="8531721983832654978">"កម្មវិធី"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"កម្មវិធី​ទាំងអស់"</string>
     <string name="notifications_header" msgid="1404149926117359025">"ការ​ជូនដំណឹង"</string>
@@ -137,7 +136,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..72eaeac 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಹುಡುಕಿ"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಲೋಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ..."</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" ಹೊಂದಿಕೆಯ ಯಾವುದೇ ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ಕಂಡುಬಂದಿಲ್ಲ"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"ಮತ್ತಷ್ಟು ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಹುಡುಕಿ"</string>
     <string name="label_application" msgid="8531721983832654978">"ಆ್ಯಪ್"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"ಎಲ್ಲಾ ಆ್ಯಪ್‌ಗಳು"</string>
     <string name="notifications_header" msgid="1404149926117359025">"ಅಧಿಸೂಚನೆಗಳು"</string>
@@ -137,7 +136,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..f6b8ff7 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"앱 검색"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"앱 로드 중…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\'<xliff:g id="QUERY">%1$s</xliff:g>\'과(와) 일치하는 앱이 없습니다."</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"더 많은 앱 검색"</string>
     <string name="label_application" msgid="8531721983832654978">"앱"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"모든 앱"</string>
     <string name="notifications_header" msgid="1404149926117359025">"알림"</string>
@@ -137,7 +136,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 5d401bc..3a58b85 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Колдонмолорду издөө"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Колдонмолор жүктөлүүдө…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" сурамына дал келген колдонмолор табылган жок"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Көбүрөөк колдонмолорду издөө"</string>
     <string name="label_application" msgid="8531721983832654978">"Колдонмо"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Бардык колдонмолор"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Билдирмелер"</string>
@@ -137,7 +136,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..d8b55f8 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"ຊອກຫາແອັບ"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"ກໍາລັງໂຫຼດແອັບ…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"ບໍ່ພົບແອັບທີ່ກົງກັບ \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"ຊອກຫາແອັບເພີ່ມເຕີມ"</string>
     <string name="label_application" msgid="8531721983832654978">"ແອັບ"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"ແອັບທັງໝົດ"</string>
     <string name="notifications_header" msgid="1404149926117359025">"ການແຈ້ງເຕືອນ"</string>
@@ -137,7 +136,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..e68362d 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Paieškos programos"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Įkeliamos programos…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Nerasta jokių užklausą „<xliff:g id="QUERY">%1$s</xliff:g>“ atitinkančių programų"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Ieškoti daugiau programų"</string>
     <string name="label_application" msgid="8531721983832654978">"Programa"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Visos programos"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Pranešimai"</string>
@@ -137,7 +136,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..75f6d1a 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Meklēt lietotnes"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Notiek lietotņu ielāde…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Vaicājumam “<xliff:g id="QUERY">%1$s</xliff:g>” neatbilda neviena lietotne"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Meklēt citas lietotnes"</string>
     <string name="label_application" msgid="8531721983832654978">"Lietotne"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Visas lietotnes"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Paziņojumi"</string>
@@ -137,7 +136,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..fef9bbe 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Пребарувајте апликации"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Се вчитуваат апликации…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Не се најдени апликации што одговараат на „<xliff:g id="QUERY">%1$s</xliff:g>“"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Пребарај други апликации"</string>
     <string name="label_application" msgid="8531721983832654978">"Апликација"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Сите апликации"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Известувања"</string>
@@ -137,7 +136,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..802ea81 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"ആപ്പുകൾ തിരയുക"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"ആപ്പുകൾ ലോഡുചെയ്യുന്നു..."</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" എന്നതുമായി പൊരുത്തപ്പെടുന്ന ആപ്പുകളൊന്നും കണ്ടെത്തിയില്ല"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"കൂടുതൽ ആപ്പുകൾക്ക് തിരയുക"</string>
     <string name="label_application" msgid="8531721983832654978">"ആപ്പ്"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"എല്ലാ ആപ്പുകളും"</string>
     <string name="notifications_header" msgid="1404149926117359025">"അറിയിപ്പുകൾ"</string>
@@ -137,7 +136,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..f091cb8 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Апп хайх"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Аппыг ачаалж байна..."</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\"-д тохирох апп олдсонгүй"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Бусад апп-г хайх"</string>
     <string name="label_application" msgid="8531721983832654978">"Апп"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Бүх апп"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Мэдэгдэл"</string>
@@ -137,7 +136,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 8625ae5..b553c90 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"अ‍ॅप्स शोधा"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"अ‍ॅप्स लोड करत आहे…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" शी जुळणारे कोणतेही अ‍ॅप्स आढळले नाहीत"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"अधिक अ‍ॅप्स शोधा"</string>
     <string name="label_application" msgid="8531721983832654978">"ॲप"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"सर्व अ‍ॅप्स"</string>
     <string name="notifications_header" msgid="1404149926117359025">"सूचना"</string>
@@ -137,7 +136,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..edd0530 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Cari apl"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Memuatkan apl…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Tiada apl yang ditemui sepadan dengan \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Cari lagi apl"</string>
     <string name="label_application" msgid="8531721983832654978">"Apl"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Semua apl"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Pemberitahuan"</string>
@@ -137,7 +136,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 d264c31..3cec97d 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"ရှာဖွေမှု အက်ပ်များ"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"အက်ပ်များကို ဖွင့်နေသည်…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" နှင့်ကိုက်ညီသည့် အပ်ပ်များကို မတွေ့ပါ"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"နောက်ထပ် အက်ပ်များကို ရှာပါ"</string>
     <string name="label_application" msgid="8531721983832654978">"အက်ပ်"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"အက်ပ်အားလုံး"</string>
     <string name="notifications_header" msgid="1404149926117359025">"အကြောင်းကြားချက်များ"</string>
@@ -137,7 +136,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..3e08d10 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Søk etter apper"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Laster inn appene …"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Fant ingen apper som samsvarer med «<xliff:g id="QUERY">%1$s</xliff:g>»"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Søk etter flere apper"</string>
     <string name="label_application" msgid="8531721983832654978">"App"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Alle apper"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Varsler"</string>
@@ -137,7 +136,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 0d149a0..6d48969 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"खोजसम्बन्धी एपहरू"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"एपहरू लोड गर्दै…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" सँग मिल्दो कुनै एप भेटिएन"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"थप एपहरू खोज्नुहोस्"</string>
     <string name="label_application" msgid="8531721983832654978">"एप"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"सबै एप"</string>
     <string name="notifications_header" msgid="1404149926117359025">"सूचनाहरू"</string>
@@ -137,7 +136,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..4b203b7 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Apps zoeken"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Apps laden…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Er zijn geen apps gevonden die overeenkomen met \'<xliff:g id="QUERY">%1$s</xliff:g>\'"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Zoeken naar meer apps"</string>
     <string name="label_application" msgid="8531721983832654978">"App"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Alle apps"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Meldingen"</string>
@@ -137,7 +136,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 9067076..c64e9b2 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"ଆପ୍‌ ଖୋଜନ୍ତୁ"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"ଆପ୍‌ ଲୋଡ୍‌ ହେଉଛି..."</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" ସହିତ ମେଳ ହେଉଥିବା କୌଣସି ଆପ୍‌ ମିଳିଲା ନାହିଁ"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"ଅଧିକ ଆପ୍‌ ଖୋଜନ୍ତୁ"</string>
     <string name="label_application" msgid="8531721983832654978">"ଆପ୍"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"ସବୁ ଆପ"</string>
     <string name="notifications_header" msgid="1404149926117359025">"ବିଜ୍ଞପ୍ତି"</string>
@@ -137,7 +136,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 e0f2138..8e55b87 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"ਐਪਾਂ ਖੋਜੋ"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"ਐਪਾਂ ਨੂੰ ਲੋਡ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ..."</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" ਨਾਲ ਮੇਲ ਖਾਂਦੀਆਂ ਕੋਈ ਐਪਾਂ ਨਹੀਂ ਮਿਲੀਆਂ"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"ਹੋਰ ਐਪਾਂ ਖੋਜੋ"</string>
     <string name="label_application" msgid="8531721983832654978">"ਐਪ"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"ਸਾਰੀਆਂ ਐਪਾਂ"</string>
     <string name="notifications_header" msgid="1404149926117359025">"ਸੂਚਨਾਵਾਂ"</string>
@@ -137,7 +136,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 b6ddbbf..8669949 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Wyszukaj aplikacje"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Ładuję aplikacje…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Nie znaleziono aplikacji pasujących do zapytania „<xliff:g id="QUERY">%1$s</xliff:g>”"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Wyszukaj więcej aplikacji"</string>
     <string name="label_application" msgid="8531721983832654978">"Aplikacja"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Wszystkie aplikacje"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Powiadomienia"</string>
@@ -137,7 +136,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 3587f7c..bb1fb36 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Pesquisar aplicações"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"A carregar aplicações…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Nenhuma app correspondente a \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Pesquisar mais aplicações"</string>
     <string name="label_application" msgid="8531721983832654978">"Aplicação"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Todas as apps"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Notificações"</string>
@@ -137,7 +136,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..98e8607 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Pesquisar apps"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Carregando apps…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Nenhum app encontrado que corresponda a \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Pesquisar mais apps"</string>
     <string name="label_application" msgid="8531721983832654978">"App"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Todos os apps"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Notificações"</string>
@@ -137,7 +136,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 9d9fb76..794609e 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Caută aplicații"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Se încarcă aplicații…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Nu s-a găsit nicio aplicație pentru „<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Caută mai multe aplicații"</string>
     <string name="label_application" msgid="8531721983832654978">"Aplicație"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Toate aplicațiile"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Notificări"</string>
@@ -137,7 +136,7 @@
     <string name="item_removed" msgid="851119963877842327">"Element eliminat"</string>
     <string name="undo" msgid="4151576204245173321">"Anulează"</string>
     <string name="action_move" msgid="4339390619886385032">"Mută elementul"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Mută 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">"Mută î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">"Mută pe poziția <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Mută î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..5987321 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Поиск приложений"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Загрузка приложений…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"По запросу \"<xliff:g id="QUERY">%1$s</xliff:g>\" ничего не найдено"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Искать другие приложения"</string>
     <string name="label_application" msgid="8531721983832654978">"Приложение"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Все приложения"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Уведомления"</string>
@@ -137,7 +136,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..e030432 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"යෙදුම් සොයන්න"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"යෙදුම් පූරණය වෙමින්…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" සමග ගැළපෙන යෙදුම් හමු නොවිණි"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"තව යෙදුම් සඳහා සොයන්න"</string>
     <string name="label_application" msgid="8531721983832654978">"යෙදුම"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"සියලු යෙදුම්"</string>
     <string name="notifications_header" msgid="1404149926117359025">"දැනුම්දීම්"</string>
@@ -137,7 +136,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 a6608f3..69fa362 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Hľadať aplikácie"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Načítavajú sa aplikácie…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Nenašli sa žiadne aplikácie zodpovedajúce dopytu <xliff:g id="QUERY">%1$s</xliff:g>"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Hľadať ďalšie aplikácie"</string>
     <string name="label_application" msgid="8531721983832654978">"Aplikácia"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Všetky aplikácie"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Upozornenia"</string>
@@ -137,7 +136,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..b1f93e8 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Iskanje programov"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Nalaganje aplikacij …"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Ni aplikacij, ki bi ustrezale poizvedbi »<xliff:g id="QUERY">%1$s</xliff:g>«"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Iskanje več aplikacij"</string>
     <string name="label_application" msgid="8531721983832654978">"Aplikacija"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Vse aplikacije"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Obvestila"</string>
@@ -137,7 +136,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..99bcbaa 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Kërko për aplikacione"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Po ngarkon aplikacionet..."</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Nuk u gjet asnjë aplikacion që përputhet me \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Kërko për më shumë aplikacione"</string>
     <string name="label_application" msgid="8531721983832654978">"Aplikacioni"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Të gjitha aplikacionet"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Njoftimet"</string>
@@ -137,7 +136,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..460a73e 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Претражите апликације"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Апликације се учитавају…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Није пронађена ниједна апликација за „<xliff:g id="QUERY">%1$s</xliff:g>“"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Претражи још апликација"</string>
     <string name="label_application" msgid="8531721983832654978">"Апликација"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Све апликације"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Обавештења"</string>
@@ -137,7 +136,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..6ec274b 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Sök efter appar"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Läser in appar …"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Inga appar som matchar <xliff:g id="QUERY">%1$s</xliff:g> hittades"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Sök efter fler appar"</string>
     <string name="label_application" msgid="8531721983832654978">"App"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Alla appar"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Aviseringar"</string>
@@ -137,7 +136,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..147c6e1 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Tafuta programu"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Inapakia programu..."</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Haikupata programu zozote zinazolingana na \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Tafuta programu zaidi"</string>
     <string name="label_application" msgid="8531721983832654978">"Programu"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Programu zote"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Arifa"</string>
@@ -137,7 +136,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-sw600dp/config.xml b/res/values-sw600dp/config.xml
index 072b92d..e718d9c 100644
--- a/res/values-sw600dp/config.xml
+++ b/res/values-sw600dp/config.xml
@@ -18,4 +18,11 @@
     <!-- The duration of the PagedView page snap animation -->
     <integer name="config_pageSnapAnimationDuration">550</integer>
 
+    <!-- The duration of the Widget picker opening and closing animation -->
+    <integer name="config_bottomSheetOpenDuration">500</integer>
+    <integer name="config_bottomSheetCloseDuration">500</integer>
+
+    <!-- The duration of the AllApps opening and closing animation -->
+    <integer name="config_allAppsOpenDuration">@integer/config_bottomSheetOpenDuration</integer>
+    <integer name="config_allAppsCloseDuration">@integer/config_bottomSheetCloseDuration</integer>
 </resources>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 0547fc2..d8eacf5 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"ஆப்ஸில் தேடுக"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"ஆப்ஸை ஏற்றுகிறது…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" உடன் பொருந்தும் ஆப்ஸ் இல்லை"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"கூடுதல் பயன்பாடுகளைத் தேடு"</string>
     <string name="label_application" msgid="8531721983832654978">"ஆப்ஸ்"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"அனைத்து ஆப்ஸும்"</string>
     <string name="notifications_header" msgid="1404149926117359025">"அறிவிப்புகள்"</string>
@@ -137,7 +136,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 8a9cfbe..e70466f 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"యాప్‌ల కోసం సెర్చ్ చేయండి"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"అప్లికేషన్‌లను లోడ్ చేస్తోంది…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\"కి సరిపోలే అప్లికేషన్‌లేవీ కనుగొనబడలేదు"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"మరిన్ని యాప్‌ల కోసం సెర్చ్ చేయండి"</string>
     <string name="label_application" msgid="8531721983832654978">"యాప్"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"అన్ని యాప్‌లు"</string>
     <string name="notifications_header" msgid="1404149926117359025">"నోటిఫికేషన్‌లు"</string>
@@ -137,7 +136,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 9e8a75e..ffeb20c 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"ค้นหาแอป"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"กำลังโหลดแอป…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"ไม่พบแอปที่ตรงกับ \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"ค้นหาแอปเพิ่มเติม"</string>
     <string name="label_application" msgid="8531721983832654978">"แอป"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"แอปทั้งหมด"</string>
     <string name="notifications_header" msgid="1404149926117359025">"การแจ้งเตือน"</string>
@@ -137,7 +136,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..a4cd808 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Maghanap ng mga app"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Naglo-load ng mga app…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Walang nahanap na app na tumutugma sa \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Maghanap ng higit pang mga app"</string>
     <string name="label_application" msgid="8531721983832654978">"App"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Lahat ng app"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Mga Notification"</string>
@@ -137,7 +136,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..c30c2e5 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Uygulamalarda ara"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Uygulamalar yükleniyor…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" ile eşleşen uygulama bulunamadı"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Başka uygulamalar ara"</string>
     <string name="label_application" msgid="8531721983832654978">"Uygulama"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Tüm uygulamalar"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Bildirimler"</string>
@@ -137,7 +136,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 f1eaeaf..2f79f23 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Пошук додатків"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Завантаження додатків…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Немає додатків для запиту \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Шукати ще додатки"</string>
     <string name="label_application" msgid="8531721983832654978">"Додаток"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Усі додатки"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Сповіщення"</string>
@@ -137,7 +136,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..3d90187 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"ایپس تلاش کریں"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"ایپس لوڈ کی جا رہی ہیں…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" سے مماثل کوئی ایپس نہیں ملیں"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"مزید ایپس تلاش کریں"</string>
     <string name="label_application" msgid="8531721983832654978">"ایپ"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"سبھی ایپس"</string>
     <string name="notifications_header" msgid="1404149926117359025">"اطلاعات"</string>
@@ -137,7 +136,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..04d07ec 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Ilovalarni qidirish"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Ilovalar yuklanmoqda…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"“<xliff:g id="QUERY">%1$s</xliff:g>” bilan mos hech qanday ilova topilmadi"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Boshqa ilovalarni qidirish"</string>
     <string name="label_application" msgid="8531721983832654978">"Ilova"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Barcha ilovalar"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Bildirishnomalar"</string>
@@ -137,7 +136,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..2329992 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Tìm kiếm ứng dụng"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Đang tải ứng dụng…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Không tìm thấy ứng dụng nào phù hợp với \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Tìm kiếm thêm ứng dụng"</string>
     <string name="label_application" msgid="8531721983832654978">"Ứng dụng"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Tất cả ứng dụng"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Thông báo"</string>
@@ -137,7 +136,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..929cffa 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"搜索应用"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"正在加载应用…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"未找到与“<xliff:g id="QUERY">%1$s</xliff:g>”相符的应用"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"搜索更多应用"</string>
     <string name="label_application" msgid="8531721983832654978">"应用"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"所有应用"</string>
     <string name="notifications_header" msgid="1404149926117359025">"通知"</string>
@@ -137,7 +136,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..1aea757 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"搜尋應用程式"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"正在載入應用程式…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"找不到與「<xliff:g id="QUERY">%1$s</xliff:g>」相符的應用程式"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"搜尋更多應用程式"</string>
     <string name="label_application" msgid="8531721983832654978">"應用程式"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"所有應用程式"</string>
     <string name="notifications_header" msgid="1404149926117359025">"通知"</string>
@@ -137,7 +136,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..25a7d21 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"搜尋應用程式"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"正在載入應用程式…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"找不到與「<xliff:g id="QUERY">%1$s</xliff:g>」相符的應用程式"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"搜尋更多應用程式"</string>
     <string name="label_application" msgid="8531721983832654978">"應用程式"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"所有應用程式"</string>
     <string name="notifications_header" msgid="1404149926117359025">"通知"</string>
@@ -137,7 +136,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..151ce27 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -59,7 +59,6 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Sesha izinhlelo zokusebenza"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Ilayisha izinhlelo zokusebenza..."</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Azikho izinhlelo zokusebenza ezitholiwe ezifana ne-\"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Sesha izinhlelo zokusebenza eziningi"</string>
     <string name="label_application" msgid="8531721983832654978">"Uhlelo lokusebenza"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Zonke izinhlelo zokusebenza"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Izaziso"</string>
@@ -137,7 +136,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..8623414 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" />
@@ -134,6 +136,10 @@
         <attr name="layout_ignoreInsets" format="boolean" />
     </declare-styleable>
 
+    <declare-styleable name="StickyScroller_Layout">
+        <attr name="layout_sticky" format="boolean" />
+    </declare-styleable>
+
     <declare-styleable name="GridDisplayOption">
         <attr name="name" format="string" />
 
@@ -152,9 +158,6 @@
 
         <!-- numHotseatIcons defaults to numColumns, if not specified -->
         <attr name="numHotseatIcons" format="integer" />
-        <!-- Number of icons to use when shrinking the hotseat size,
-         defaults to numHotseatIcons / 2 -->
-        <attr name="numShrunkenHotseatIcons" format="integer" />
         <!-- Number of icons to use when extending the hotseat size,
          defaults to 2 * numHotseatIcons -->
         <attr name="numExtendedHotseatIcons" format="integer" />
@@ -170,6 +173,10 @@
         <!-- defaults to numColumns, if not specified -->
         <attr name="hotseatColumnSpanTwoPanelPortrait" format="integer" />
 
+        <!-- Spacing to have at the end of the nav buttons in large screen 3 button nav,
+             defaults to @dimen/taskbar_button_margin_default -->
+        <attr name="inlineNavButtonsEndSpacing" format="reference" />
+
         <attr name="dbFile" format="string" />
         <attr name="defaultLayoutId" format="reference" />
         <attr name="defaultSplitDisplayLayoutId" format="reference" />
@@ -177,7 +184,7 @@
         <attr name="isScalable" format="boolean" />
         <attr name="devicePaddingId" format="reference" />
         <!-- By default all categories are enabled -->
-        <attr name="deviceCategory" format="integer" >
+        <attr name="deviceCategory" format="integer">
             <!-- Enable on phone only -->
             <flag name="phone" value="1" />
             <!-- Enable on tablets only -->
@@ -186,6 +193,18 @@
             <flag name="multi_display" value="4" />
         </attr>
 
+        <!-- By default all are false -->
+        <attr name="inlineQsb" format="integer">
+            <!-- Enable on landscape only -->
+            <flag name="portrait" value="1" />
+            <!-- Enable on portrait only -->
+            <flag name="landscape" value="2" />
+            <!-- Enable on two panel portrait only -->
+            <flag name="twoPanelPortrait" value="4" />
+            <!-- Enable on two panel landscape only -->
+            <flag name="twoPanelLandscape" value="8" />
+        </attr>
+
     </declare-styleable>
 
     <declare-styleable name="DevicePadding">
@@ -252,9 +271,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 -->
@@ -273,6 +293,8 @@
         <!-- defaults to iconImageSize, if not specified -->
         <attr name="allAppsIconSize" format="float" />
         <!-- defaults to allAppsIconSize, if not specified -->
+        <attr name="allAppsIconSizeLandscape" format="float" />
+        <!-- defaults to allAppsIconSize, if not specified -->
         <attr name="allAppsIconSizeTwoPanelPortrait" format="float" />
         <!-- defaults to allAppsIconSize, if not specified -->
         <attr name="allAppsIconSizeTwoPanelLandscape" format="float" />
@@ -317,14 +339,36 @@
         if not specified -->
         <attr name="allAppsBorderSpaceTwoPanelLandscapeVertical" format="float" />
 
-        <!-- defaults to borderSpaceDps, if not specified -->
-        <attr name="hotseatBorderSpace" format="float" />
-        <!-- defaults to hotseatBorderSpace, if not specified -->
-        <attr name="hotseatBorderSpaceLandscape" format="float" />
-        <!-- defaults to hotseatBorderSpace, if not specified -->
-        <attr name="hotseatBorderSpaceTwoPanelLandscape" format="float" />
-        <!-- defaults to hotseatBorderSpace, if not specified -->
-        <attr name="hotseatBorderSpaceTwoPanelPortrait" format="float" />
+        <!-- defaults to minCellHeight if not specified
+        when GridDisplayOption#isScalable is true. -->
+        <attr name="folderCellHeight" format="float" />
+        <!-- defaults to minCellWidth, if not specified -->
+        <attr name="folderCellWidth" format="float" />
+
+        <!-- defaults to borderSpace, if not specified -->
+        <!-- space to be used horizontally and vertically -->
+        <attr name="folderBorderSpace" format="float" />
+
+        <!-- defaults to folderBorderSpace vertical, if not specified -->
+        <attr name="folderTopPadding" 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 -->
@@ -354,17 +398,6 @@
         <!-- defaults to horizontalMargin if not specified -->
         <attr name="horizontalMarginTwoPanelPortrait" format="float"/>
 
-        <!-- By default all are false -->
-        <attr name="inlineQsb" format="integer" >
-            <!-- Enable on landscape only -->
-            <flag name="portrait" value="1" />
-            <!-- Enable on portrait only -->
-            <flag name="landscape" value="2" />
-            <!-- Enable on two panel portrait only -->
-            <flag name="twoPanelPortrait" value="4" />
-            <!-- Enable on two panel landscape only -->
-            <flag name="twoPanelLandscape" value="8" />
-        </attr>
     </declare-styleable>
 
     <declare-styleable name="CellLayout">
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..11b6e8c 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -85,6 +85,7 @@
     <string name="launcher_activity_logic_class" translatable="false"></string>
     <string name="model_delegate_class" translatable="false"></string>
     <string name="window_manager_proxy_class" translatable="false"></string>
+    <string name="secondary_display_predictions_class" translatable="false"></string>
 
     <!-- View ID to use for QSB widget -->
     <item type="id" name="qsb_widget" />
@@ -130,6 +131,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"/>
 
@@ -151,6 +157,7 @@
 
     <item name="swipe_up_rect_scale_damping_ratio" type="dimen" format="float">0.75</item>
     <item name="swipe_up_rect_scale_stiffness" type="dimen" format="float">200</item>
+    <item name="swipe_up_rect_scale_higher_stiffness" type="dimen" format="float">400</item>
 
     <item name="swipe_up_rect_xy_fling_friction" type="dimen" format="float">1.5</item>
 
@@ -186,4 +193,18 @@
     <dimen name="swipe_back_window_scale_x_margin">10dp</dimen>
     <dimen name="swipe_back_window_max_delta_y">160dp</dimen>
     <dimen name="swipe_back_window_corner_radius">40dp</dimen>
+
+    <!-- The duration of the bottom sheet opening and closing animation -->
+    <integer name="config_bottomSheetOpenDuration">267</integer>
+    <integer name="config_bottomSheetCloseDuration">267</integer>
+
+    <!-- The duration of the AllApps opening and closing animation -->
+    <integer name="config_allAppsOpenDuration">600</integer>
+    <integer name="config_allAppsCloseDuration">300</integer>
+
+    <!-- The max scale for the wallpaper when it's zoomed in -->
+    <item name="config_wallpaperMaxScale" format="float" type="dimen">0</item>
+
+    <string name="floating_task_package" translatable="false"></string>
+    <string name="floating_task_action" translatable="false"></string>
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 8403af4..5dae9f2 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -22,8 +22,6 @@
     <dimen name="dynamic_grid_edge_margin">10.77dp</dimen>
     <dimen name="dynamic_grid_left_right_margin">8dp</dimen>
     <dimen name="dynamic_grid_icon_drawable_padding">7dp</dimen>
-    <!-- Minimum space between workspace and hotseat in spring loaded mode -->
-    <dimen name="dynamic_grid_min_spring_loaded_space">8dp</dimen>
     <!-- Minimum amount of next page visible in spring loaded mode -->
     <dimen name="dynamic_grid_spring_loaded_min_next_space_visible">24dp</dimen>
 
@@ -32,10 +30,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 +39,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>
@@ -116,9 +108,6 @@
     <dimen name="all_apps_search_bar_content_overlap">24dp</dimen>
     <dimen name="all_apps_search_bar_bottom_padding">30dp</dimen>
     <dimen name="all_apps_empty_search_message_top_offset">40dp</dimen>
-    <dimen name="all_apps_empty_search_bg_top_offset">144dp</dimen>
-    <dimen name="all_apps_background_canvas_width">700dp</dimen>
-    <dimen name="all_apps_background_canvas_height">475dp</dimen>
     <dimen name="all_apps_header_pill_height">48dp</dimen>
     <dimen name="all_apps_header_pill_corner_radius">12dp</dimen>
     <dimen name="all_apps_header_tab_height">48dp</dimen>
@@ -132,6 +121,7 @@
     <dimen name="all_apps_work_profile_tab_footer_bottom_padding">20dp</dimen>
     <dimen name="all_apps_tabs_button_horizontal_padding">4dp</dimen>
     <dimen name="all_apps_tabs_vertical_padding">6dp</dimen>
+    <dimen name="all_apps_tabs_margin_top">8dp</dimen>
     <dimen name="all_apps_divider_height">2dp</dimen>
     <dimen name="all_apps_divider_width">128dp</dimen>
     <dimen name="all_apps_content_fade_in_offset">150dp</dimen>
@@ -139,9 +129,9 @@
     <dimen name="all_apps_height_extra">6dp</dimen>
     <dimen name="all_apps_bottom_sheet_horizontal_padding">0dp</dimen>
     <dimen name="all_apps_paged_view_top_padding">40dp</dimen>
-    <dimen name="all_apps_personal_work_tabs_vertical_margin">16dp</dimen>
 
     <dimen name="all_apps_icon_drawable_padding">8dp</dimen>
+    <dimen name="all_apps_predicted_icon_vertical_padding">8dp</dimen>
     <!-- The size of corner radius of the arrow in the arrow toast. -->
     <dimen name="arrow_toast_corner_radius">2dp</dimen>
     <dimen name="arrow_toast_elevation">2dp</dimen>
@@ -237,6 +227,7 @@
     <dimen name="drop_target_text_size">16sp</dimen>
     <dimen name="drop_target_shadow_elevation">2dp</dimen>
     <dimen name="drop_target_bar_margin_horizontal">4dp</dimen>
+    <dimen name="drop_target_button_drawable_size">20dp</dimen>
     <dimen name="drop_target_button_drawable_padding">8dp</dimen>
     <dimen name="drop_target_button_drawable_horizontal_padding">16dp</dimen>
     <dimen name="drop_target_button_drawable_vertical_padding">8dp</dimen>
@@ -257,12 +248,14 @@
 
     <!-- Folders -->
     <dimen name="page_indicator_dot_size">8dp</dimen>
+    <dimen name="page_indicator_current_page_indicator_size">10dp</dimen>
+
 
     <dimen name="folder_cell_x_padding">9dp</dimen>
     <dimen name="folder_cell_y_padding">6dp</dimen>
     <!-- label text size = workspace text size multiplied by this scale -->
     <dimen name="folder_label_text_scale">1.14</dimen>
-    <dimen name="folder_label_height">48dp</dimen>
+    <dimen name="folder_label_height">56dp</dimen>
 
     <dimen name="folder_content_padding_left_right">8dp</dimen>
     <dimen name="folder_content_padding_top">16dp</dimen>
@@ -364,9 +357,21 @@
     <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="min_hotseat_icon_space">18dp</dimen>
+    <dimen name="min_hotseat_qsb_width">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>
+    <dimen name="taskbar_nav_buttons_size">0dp</dimen>
+    <dimen name="taskbar_contextual_button_margin">0dp</dimen>
+    <dimen name="taskbar_hotseat_nav_spacing">0dp</dimen>
+    <dimen name="taskbar_button_margin_default">0dp</dimen>
+    <dimen name="taskbar_button_space_inbetween">0dp</dimen>
+    <dimen name="taskbar_button_margin_5_5">0dp</dimen>
+    <dimen name="taskbar_button_margin_6_5">0dp</dimen>
+    <dimen name="taskbar_button_margin_4_5">0dp</dimen>
+    <dimen name="taskbar_button_margin_4_4">0dp</dimen>
 
     <!-- Size of the maximum radius for the enforced rounded rectangles. -->
     <dimen name="enforced_rounded_corner_max_radius">16dp</dimen>
@@ -379,7 +384,6 @@
     <dimen name="task_thumbnail_icon_drawable_size">0dp</dimen>
     <dimen name="task_thumbnail_icon_drawable_size_grid">0dp</dimen>
     <dimen name="overview_task_margin">0dp</dimen>
-    <dimen name="overview_task_margin_grid">0dp</dimen>
     <dimen name="overview_actions_height">0dp</dimen>
     <dimen name="overview_actions_button_spacing">0dp</dimen>
     <dimen name="overview_actions_margin_gesture">0dp</dimen>
@@ -391,8 +395,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>
@@ -405,8 +418,12 @@
 
     <!-- Bottom sheet related parameters -->
     <dimen name="bottom_sheet_extra_top_padding">0dp</dimen>
+    <dimen name="bottom_sheet_handle_area_height">36dp</dimen>
     <dimen name="bottom_sheet_handle_width">32dp</dimen>
     <dimen name="bottom_sheet_handle_height">4dp</dimen>
     <dimen name="bottom_sheet_handle_margin">16dp</dimen>
     <dimen name="bottom_sheet_handle_corner_radius">2dp</dimen>
+
+    <!-- State transition -->
+    <item name="workspace_content_scale" format="float" type="dimen">0.97</item>
 </resources>
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..3eb08ba 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -128,8 +128,6 @@
     <string name="all_apps_loading_message">Loading apps&#8230;</string>
     <!-- No-search-results text. [CHAR_LIMIT=50] -->
     <string name="all_apps_no_search_results">No apps found matching \"<xliff:g id="query" example="Android">%1$s</xliff:g>\"</string>
-    <!-- Label for the button which allows the user to get app search results. [CHAR_LIMIT=50] -->
-    <string name="all_apps_search_market_message">Search for more apps</string>
     <!-- Label for an icon representing any generic app. [CHAR_LIMIT=50] -->
     <string name="label_application">App</string>
     <!-- Label for the header text of the All Apps section in All Apps view, used to separate Predicted Apps and Actions section from All Apps section. [CHAR_LIMIT=50] -->
@@ -344,7 +342,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..90553a1 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">
@@ -270,7 +274,7 @@
     </style>
 
     <!-- Drop targets -->
-    <style name="DropTargetButtonBase" parent="@android:style/TextAppearance.DeviceDefault">
+    <style name="DropTargetButtonBase" parent="@android:style/TextAppearance.DeviceDefault.Medium">
         <item name="android:drawablePadding">@dimen/drop_target_button_drawable_padding</item>
         <item name="android:padding">14dp</item>
         <item name="android:textColor">@color/drop_target_text</item>
@@ -288,17 +292,6 @@
 
     <style name="TextTitle" parent="@android:style/TextAppearance.DeviceDefault" />
 
-    <style name="AllAppsEmptySearchBackground">
-        <item name="android:colorPrimary">#E0E0E0</item>
-        <item name="android:colorControlHighlight">#19BDBDBD</item>
-        <item name="android:colorForeground">@color/all_apps_bg_hand_fill</item>
-    </style>
-    <style name="AllAppsEmptySearchBackground.Dark">
-        <item name="android:colorPrimary">#9AA0A6</item>
-        <item name="android:colorControlHighlight">#19DFE1E5</item>
-        <item name="android:colorForeground">@color/all_apps_bg_hand_fill_dark</item>
-    </style>
-
     <style name="Button.TopRounded.Bordered" parent="@android:style/Widget.Material.Button">
         <item name="android:background">@drawable/button_top_rounded_bordered_ripple</item>
         <item name="android:stateListAnimator">@null</item>
diff --git a/res/xml/device_profiles.xml b/res/xml/device_profiles.xml
index 0802552..407f217 100644
--- a/res/xml/device_profiles.xml
+++ b/res/xml/device_profiles.xml
@@ -26,7 +26,7 @@
         launcher:numHotseatIcons="3"
         launcher:dbFile="launcher_3_by_3.db"
         launcher:defaultLayoutId="@xml/default_workspace_3x3"
-        launcher:deviceCategory="phone|multi_display" >
+        launcher:deviceCategory="phone" >
 
         <display-option
             launcher:name="Super Short Stubby"
@@ -58,6 +58,7 @@
         launcher:numFolderColumns="4"
         launcher:numHotseatIcons="4"
         launcher:dbFile="launcher_4_by_4.db"
+        launcher:inlineNavButtonsEndSpacing="@dimen/taskbar_button_margin_4_4"
         launcher:defaultLayoutId="@xml/default_workspace_4x4"
         launcher:deviceCategory="phone|multi_display" >
 
@@ -121,6 +122,7 @@
         launcher:numFolderColumns="4"
         launcher:numHotseatIcons="5"
         launcher:dbFile="launcher.db"
+        launcher:inlineNavButtonsEndSpacing="@dimen/taskbar_button_margin_5_5"
         launcher:defaultLayoutId="@xml/default_workspace_5x5"
         launcher:deviceCategory="phone|multi_display" >
 
@@ -167,6 +169,7 @@
         launcher:hotseatColumnSpanLandscape="4"
         launcher:numAllAppsColumns="6"
         launcher:isScalable="true"
+        launcher:inlineNavButtonsEndSpacing="@dimen/taskbar_button_margin_6_5"
         launcher:devicePaddingId="@xml/paddings_6x5"
         launcher:dbFile="launcher_6_by_5.db"
         launcher:defaultLayoutId="@xml/default_workspace_6x5"
@@ -197,8 +200,8 @@
             launcher:allAppsBorderSpaceHorizontal="8"
             launcher:allAppsBorderSpaceVertical="16"
             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/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 21dbc5f..5ee6fce 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -109,10 +109,6 @@
             | TYPE_ALL_APPS_EDU | TYPE_ICON_SURFACE | TYPE_WIDGETS_EDUCATION_DIALOG
             | TYPE_TASKBAR_EDUCATION_DIALOG | TYPE_TASKBAR_ALL_APPS | TYPE_OPTIONS_POPUP_DIALOG;
 
-    // Usually we show the back button when a floating view is open. Instead, hide for these types.
-    public static final int TYPE_HIDE_BACK_BUTTON = TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE
-            | TYPE_SNACKBAR | TYPE_WIDGET_RESIZE_FRAME | TYPE_LISTENER;
-
     public static final int TYPE_ACCESSIBLE = TYPE_ALL & ~TYPE_DISCOVERY_BOUNCE & ~TYPE_LISTENER
             & ~TYPE_ALL_APPS_EDU;
 
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/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index 4b4a017..fbb0a57 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -24,6 +24,7 @@
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.ImageButton;
 import android.widget.ImageView;
 
@@ -68,22 +69,6 @@
 
     private final View[] mDragHandles = new View[HANDLE_COUNT];
     private final List<Rect> mSystemGestureExclusionRects = new ArrayList<>(HANDLE_COUNT);
-    private final OnAttachStateChangeListener mWidgetViewAttachStateChangeListener =
-            new OnAttachStateChangeListener() {
-                @Override
-                public void onViewAttachedToWindow(View view) {
-                    // Do nothing
-                }
-
-                @Override
-                public void onViewDetachedFromWindow(View view) {
-                    // When the app widget view is detached, we should close the resize frame.
-                    // An example is when the dragging starts, the widget view is detached from
-                    // CellLayout and then reattached to DragLayout.
-                    close(false);
-                }
-            };
-
 
     private LauncherAppWidgetHostView mWidgetView;
     private CellLayout mCellLayout;
@@ -221,11 +206,7 @@
     private void setupForWidget(LauncherAppWidgetHostView widgetView, CellLayout cellLayout,
             DragLayer dragLayer) {
         mCellLayout = cellLayout;
-        if (mWidgetView != null) {
-            mWidgetView.removeOnAttachStateChangeListener(mWidgetViewAttachStateChangeListener);
-        }
         mWidgetView = widgetView;
-        mWidgetView.addOnAttachStateChangeListener(mWidgetViewAttachStateChangeListener);
         LauncherAppWidgetProviderInfo info = (LauncherAppWidgetProviderInfo)
                 widgetView.getAppWidgetInfo();
         mDragLayer = dragLayer;
@@ -423,6 +404,10 @@
      *  Based on the current deltas, we determine if and how to resize the widget.
      */
     private void resizeWidgetIfNeeded(boolean onDismiss) {
+        ViewGroup.LayoutParams wlp = mWidgetView.getLayoutParams();
+        if (!(wlp instanceof CellLayout.LayoutParams)) {
+            return;
+        }
         DeviceProfile dp = mLauncher.getDeviceProfile();
         float xThreshold = mCellLayout.getCellWidth() + dp.cellLayoutBorderSpacePx.x;
         float yThreshold = mCellLayout.getCellHeight() + dp.cellLayoutBorderSpacePx.y;
@@ -435,7 +420,7 @@
         mDirectionVector[0] = 0;
         mDirectionVector[1] = 0;
 
-        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mWidgetView.getLayoutParams();
+        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) wlp;
 
         int spanX = lp.cellHSpan;
         int spanY = lp.cellVSpan;
@@ -687,9 +672,6 @@
     @Override
     protected void handleClose(boolean animate) {
         mDragLayer.removeView(this);
-        if (mWidgetView != null) {
-            mWidgetView.removeOnAttachStateChangeListener(mWidgetViewAttachStateChangeListener);
-        }
     }
 
     private void updateInvalidResizeEffect(CellLayout cellLayout, CellLayout pairedCellLayout,
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 73d3e33..83ff084 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -25,12 +25,16 @@
 import android.content.ContextWrapper;
 import android.content.Intent;
 import android.content.res.Configuration;
+import android.os.Bundle;
+import android.window.OnBackInvokedDispatcher;
 
 import androidx.annotation.IntDef;
 
 import com.android.launcher3.DeviceProfile.DeviceProfileListenable;
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.ViewCache;
 import com.android.launcher3.views.AppLauncher;
@@ -77,8 +81,8 @@
             new ArrayList<>();
 
     protected DeviceProfile mDeviceProfile;
-    protected StatsLogManager mStatsLogManager;
     protected SystemUiController mSystemUiController;
+    private StatsLogManager mStatsLogManager;
 
 
     public static final int ACTIVITY_STATE_STARTED = 1 << 0;
@@ -172,6 +176,19 @@
     }
 
     @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (Utilities.ATLEAST_T) {
+            getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
+                    OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+                    () -> {
+                        onBackPressed();
+                        TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onBackInvoked");
+                    });
+        }
+    }
+
+    @Override
     protected void onStart() {
         addActivityFlags(ACTIVITY_STATE_STARTED);
         super.onStart();
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index 3b24df2..5abe3d3 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -91,7 +91,7 @@
 
         Resources resources = getResources();
         mDragDistanceThreshold = resources.getDimensionPixelSize(R.dimen.drag_distanceThreshold);
-        mDrawableSize = resources.getDimensionPixelSize(R.dimen.drop_target_text_size);
+        mDrawableSize = resources.getDimensionPixelSize(R.dimen.drop_target_button_drawable_size);
         mDrawablePadding = resources.getDimensionPixelSize(
                 R.dimen.drop_target_button_drawable_padding);
     }
@@ -374,11 +374,63 @@
         hideTooltip();
     }
 
+    /**
+     * Returns if the text will be truncated within the provided availableWidth.
+     */
     public boolean isTextTruncated(int availableWidth) {
-        availableWidth -= (getPaddingLeft() + getPaddingRight() + mDrawable.getIntrinsicWidth()
-                + getCompoundDrawablePadding());
-        CharSequence displayedText = TextUtils.ellipsize(mText, getPaint(), availableWidth,
+        availableWidth -= getPaddingLeft() + getPaddingRight();
+        if (mIconVisible) {
+            availableWidth -= mDrawable.getIntrinsicWidth() + getCompoundDrawablePadding();
+        }
+        if (availableWidth <= 0) {
+            return true;
+        }
+        CharSequence firstLine = TextUtils.ellipsize(mText, getPaint(), availableWidth,
                 TextUtils.TruncateAt.END);
-        return !mText.equals(displayedText);
+        if (!mTextMultiLine) {
+            return !TextUtils.equals(mText, firstLine);
+        }
+        if (TextUtils.equals(mText, firstLine)) {
+            // When multi-line is active, if it can display as one line, then text is not truncated.
+            return false;
+        }
+        CharSequence secondLine =
+                TextUtils.ellipsize(mText.subSequence(firstLine.length(), mText.length()),
+                        getPaint(), availableWidth, TextUtils.TruncateAt.END);
+        return !(TextUtils.equals(mText.subSequence(0, firstLine.length()), firstLine)
+                && TextUtils.equals(mText.subSequence(firstLine.length(), secondLine.length()),
+                secondLine));
+    }
+
+    /**
+     * Reduce the size of the text until it fits the measured width or reaches a minimum.
+     *
+     * The minimum size is defined by {@code R.dimen.button_drop_target_min_text_size} and
+     * it diminishes by intervals defined by
+     * {@code R.dimen.button_drop_target_resize_text_increment}
+     * This functionality is very similar to the option
+     * {@link TextView#setAutoSizeTextTypeWithDefaults(int)} but can't be used in this view because
+     * the layout width is {@code WRAP_CONTENT}.
+     *
+     * @return The biggest text size in SP that makes the text fit or if the text can't fit returns
+     *         the min available value
+     */
+    public float resizeTextToFit() {
+        float minSize = Utilities.pxToSp(getResources()
+                .getDimensionPixelSize(R.dimen.button_drop_target_min_text_size));
+        float step = Utilities.pxToSp(getResources()
+                .getDimensionPixelSize(R.dimen.button_drop_target_resize_text_increment));
+        float textSize = Utilities.pxToSp(getTextSize());
+
+        int availableWidth = getMeasuredWidth();
+        while (textSize > minSize) {
+            if (isTextTruncated(availableWidth)) {
+                textSize -= step;
+                setTextSize(textSize);
+            } else {
+                return textSize;
+            }
+        }
+        return minSize;
     }
 }
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 300e7bf..9f3e1fa 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -496,8 +496,8 @@
 
                     // Draw reorder drag target.
                     debugPaint.setColor(Color.RED);
-                    canvas.drawCircle(cellCenter[0], cellCenter[1], getReorderRadius(targetCell),
-                            debugPaint);
+                    canvas.drawCircle(cellCenter[0], cellCenter[1],
+                            getReorderRadius(targetCell, 1, 1), debugPaint);
 
                     // Draw folder creation drag target.
                     if (canCreateFolder) {
@@ -829,8 +829,8 @@
         final int hStartPadding = getPaddingLeft();
         final int vStartPadding = getPaddingTop();
 
-        result[0] = (x - hStartPadding) / mCellWidth;
-        result[1] = (y - vStartPadding) / mCellHeight;
+        result[0] = (x - hStartPadding) / (mCellWidth + mBorderSpace.x);
+        result[1] = (y - vStartPadding) / (mCellHeight + mBorderSpace.y);
 
         final int xAxis = mCountX;
         final int yAxis = mCountY;
@@ -842,16 +842,6 @@
     }
 
     /**
-     * Given a point, return the cell that most closely encloses that point
-     * @param x X coordinate of the point
-     * @param y Y coordinate of the point
-     * @param result Array of 2 ints to hold the x and y coordinate of the cell
-     */
-    void pointToCellRounded(int x, int y, int[] result) {
-        pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
-    }
-
-    /**
      * Given a cell coordinate, return the point that represents the upper left corner of that cell
      *
      * @param cellX X coordinate of the cell
@@ -922,18 +912,18 @@
         DeviceProfile grid = mActivity.getDeviceProfile();
         float iconVisibleRadius = ICON_VISIBLE_AREA_FACTOR * grid.iconSizePx / 2;
         // Halfway between reorder radius and icon.
-        return (getReorderRadius(targetCell) + iconVisibleRadius) / 2;
+        return (getReorderRadius(targetCell, 1, 1) + iconVisibleRadius) / 2;
     }
 
     /**
      * Returns the max distance from the center of a cell that will start to reorder on drag over.
      */
-    public float getReorderRadius(int[] targetCell) {
+    public float getReorderRadius(int[] targetCell, int spanX, int spanY) {
         int[] centerPoint = mTmpPoint;
         getWorkspaceCellVisualCenter(targetCell[0], targetCell[1], centerPoint);
 
         Rect cellBoundsWithSpacing = mTempRect;
-        cellToRect(targetCell[0], targetCell[1], 1, 1, cellBoundsWithSpacing);
+        cellToRect(targetCell[0], targetCell[1], spanX, spanY, cellBoundsWithSpacing);
         cellBoundsWithSpacing.inset(-mBorderSpace.x / 2, -mBorderSpace.y / 2);
 
         if (canCreateFolder(getChildAt(targetCell[0], targetCell[1]))) {
@@ -1202,13 +1192,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));
         }
     }
 
@@ -1239,7 +1230,7 @@
      */
     int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
             int spanY, int[] result, int[] resultSpan) {
-        return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, true,
+        return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, false,
                 result, resultSpan);
     }
 
@@ -1261,9 +1252,10 @@
     /**
      * Find a vacant area that will fit the given bounds nearest the requested
      * cell location. Uses Euclidean distance to score multiple vacant areas.
-     *
-     * @param pixelX The X location at which you want to search for a vacant area.
-     * @param pixelY The Y location at which you want to search for a vacant area.
+     * @param relativeXPos The X location relative to the Cell layout at which you want to search
+     *                     for a vacant area.
+     * @param relativeYPos The Y location relative to the Cell layout at which you want to search
+     *                     for a vacant area.
      * @param minSpanX The minimum horizontal span required
      * @param minSpanY The minimum vertical span required
      * @param spanX Horizontal span of the object.
@@ -1274,15 +1266,15 @@
      * @return The X, Y cell of a vacant area that can contain this object,
      *         nearest the requested location.
      */
-    private int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
-            int spanY, boolean ignoreOccupied, int[] result, int[] resultSpan) {
+    private int[] findNearestArea(int relativeXPos, int relativeYPos, int minSpanX, int minSpanY,
+            int spanX, int spanY, boolean ignoreOccupied, int[] result, int[] resultSpan) {
         lazyInitTempRectStack();
 
-        // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
-        // to the center of the item, but we are searching based on the top-left cell, so
-        // we translate the point over to correspond to the top-left.
-        pixelX -= mCellWidth * (spanX - 1) / 2f;
-        pixelY -= mCellHeight * (spanY - 1) / 2f;
+        // For items with a spanX / spanY > 1, the passed in point (relativeXPos, relativeYPos)
+        // corresponds to the center of the item, but we are searching based on the top-left cell,
+        // so we translate the point over to correspond to the top-left.
+        relativeXPos = (int) (relativeXPos - (mCellWidth + mBorderSpace.x) * (spanX - 1) / 2f);
+        relativeYPos = (int) (relativeYPos - (mCellHeight + mBorderSpace.y) * (spanY - 1) / 2f);
 
         // Keep track of best-scoring drop area
         final int[] bestXY = result != null ? result : new int[2];
@@ -1303,7 +1295,7 @@
             for (int x = 0; x < countX - (minSpanX - 1); x++) {
                 int ySize = -1;
                 int xSize = -1;
-                if (ignoreOccupied) {
+                if (!ignoreOccupied) {
                     // First, let's see if this thing fits anywhere
                     for (int i = 0; i < minSpanX; i++) {
                         for (int j = 0; j < minSpanY; j++) {
@@ -1367,7 +1359,7 @@
                     }
                 }
                 validRegions.push(currentRect);
-                double distance = Math.hypot(cellXY[0] - pixelX,  cellXY[1] - pixelY);
+                double distance = Math.hypot(cellXY[0] - relativeXPos,  cellXY[1] - relativeYPos);
 
                 if ((distance <= bestDistance && !contained) ||
                         currentRect.contains(bestRect)) {
@@ -2628,7 +2620,7 @@
      *         nearest the requested location.
      */
     public int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) {
-        return findNearestArea(pixelX, pixelY, spanX, spanY, spanX, spanY, false, result, null);
+        return findNearestArea(pixelX, pixelY, spanX, spanY, spanX, spanY, true, result, null);
     }
 
     boolean existsEmptyCell() {
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index b276397..4cad919 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -20,27 +20,32 @@
 import static com.android.launcher3.InvariantDeviceProfile.INDEX_LANDSCAPE;
 import static com.android.launcher3.InvariantDeviceProfile.INDEX_TWO_PANEL_LANDSCAPE;
 import static com.android.launcher3.InvariantDeviceProfile.INDEX_TWO_PANEL_PORTRAIT;
-import static com.android.launcher3.ResourceUtils.pxFromDp;
 import static com.android.launcher3.Utilities.dpiFromPx;
 import static com.android.launcher3.Utilities.pxFromSp;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR;
+import static com.android.launcher3.icons.GraphicsUtils.getShapePath;
+import static com.android.launcher3.testing.shared.ResourceUtils.pxFromDp;
 
 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.graphics.Path;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.util.DisplayMetrics;
+import android.util.SparseArray;
 import android.view.Surface;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.CellLayout.ContainerType;
 import com.android.launcher3.DevicePaddings.DevicePadding;
 import com.android.launcher3.icons.DotRenderer;
-import com.android.launcher3.icons.GraphicsUtils;
 import com.android.launcher3.icons.IconNormalizer;
+import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.uioverrides.ApiWrapper;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.Info;
@@ -48,11 +53,17 @@
 
 import java.io.PrintWriter;
 import java.util.List;
+import java.util.Locale;
 
 @SuppressLint("NewApi")
 public class DeviceProfile {
 
     private static final int DEFAULT_DOT_SIZE = 100;
+    private static final float ALL_APPS_TABLET_MAX_ROWS = 5.5f;
+
+    public static final PointF DEFAULT_SCALE = new PointF(1.0f, 1.0f);
+    public static final ViewScaleProvider DEFAULT_PROVIDER = itemInfo -> DEFAULT_SCALE;
+
     // Ratio of empty space, qsb should take up to appear visually centered.
     private final float mQsbCenterFactor;
 
@@ -107,15 +118,12 @@
     public Rect cellLayoutPaddingPx = new Rect();
 
     public final int edgeMarginPx;
-    public float workspaceSpringLoadShrunkTop;
-    public float workspaceSpringLoadShrunkBottom;
-    public final int workspaceSpringLoadedBottomSpace;
+    public final float workspaceContentScale;
     public final int workspaceSpringLoadedMinNextPageVisiblePx;
 
     private final int extraSpace;
     public int workspaceTopPadding;
     public int workspaceBottomPadding;
-    public int extraHotseatBottomPadding;
 
     // Workspace page indicator
     public final int workspacePageIndicatorHeight;
@@ -143,7 +151,6 @@
 
     // Folder content
     public Point folderCellLayoutBorderSpacePx;
-    public int folderCellLayoutBorderSpaceOriginalPx;
     public int folderContentPaddingLeftRight;
     public int folderContentPaddingTop;
 
@@ -157,31 +164,37 @@
     public int folderChildDrawablePaddingPx;
 
     // Hotseat
-    public int hotseatBarSizeExtraSpacePx;
-    public final int numShownHotseatIcons;
+    public int numShownHotseatIcons;
     public int hotseatCellHeightPx;
-    private final int hotseatExtraVerticalSize;
-    private final boolean areNavButtonsInline;
+    public 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 hotseatBarEndOffset;
+    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 int hotseatQsbWidth; // only used when isQsbInline
     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
+    // Bottom sheets
+    public int bottomSheetTopPadding;
+    public int bottomSheetOpenDuration;
+    public int bottomSheetCloseDuration;
+    public float bottomSheetWorkspaceScale;
+    public float bottomSheetDepth;
 
     // All apps
     public Point allAppsBorderSpacePx;
     public int allAppsShiftRange;
     public int allAppsTopPadding;
-    public int bottomSheetTopPadding;
+    public int allAppsOpenDuration;
+    public int allAppsCloseDuration;
     public int allAppsCellHeightPx;
     public int allAppsCellWidthPx;
     public int allAppsIconSizePx;
@@ -193,7 +206,6 @@
 
     // Overview
     public int overviewTaskMarginPx;
-    public int overviewTaskMarginGridPx;
     public int overviewTaskIconSizePx;
     public int overviewTaskIconDrawableSizePx;
     public int overviewTaskIconDrawableSizeGridPx;
@@ -206,7 +218,7 @@
     public int overviewGridSideMargin;
 
     // Widgets
-    public final PointF appWidgetScale = new PointF(1.0f, 1.0f);
+    private final ViewScaleProvider mViewScaleProvider;
 
     // Drop Target
     public int dropTargetBarSizePx;
@@ -222,13 +234,12 @@
     // Insets
     private final Rect mInsets = new Rect();
     public final Rect workspacePadding = new Rect();
-    private final Rect mHotseatPadding = new Rect();
     // When true, nav bar is on the left side of the screen.
     private boolean mIsSeascape;
 
     // Notification dots
-    public DotRenderer mDotRendererWorkSpace;
-    public DotRenderer mDotRendererAllApps;
+    public final DotRenderer mDotRendererWorkSpace;
+    public final DotRenderer mDotRendererAllApps;
 
     // Taskbar
     public boolean isTaskbarPresent;
@@ -242,8 +253,9 @@
 
     /** TODO: Once we fully migrate to staged split, remove "isMultiWindowMode" */
     DeviceProfile(Context context, InvariantDeviceProfile inv, Info info, WindowBounds windowBounds,
-            boolean isMultiWindowMode, boolean transposeLayoutWithOrientation,
-            boolean useTwoPanels, boolean isGestureMode) {
+            SparseArray<DotRenderer> dotRendererCache, boolean isMultiWindowMode,
+            boolean transposeLayoutWithOrientation, boolean useTwoPanels, boolean isGestureMode,
+            @NonNull final ViewScaleProvider viewScaleProvider) {
 
         this.inv = inv;
         this.isLandscape = windowBounds.isLandscape();
@@ -275,7 +287,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;
@@ -301,6 +313,7 @@
         }
 
         edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
+        workspaceContentScale = res.getFloat(R.dimen.workspace_content_scale);
 
         desiredWorkspaceHorizontalMarginPx = getHorizontalMarginPx(inv, res);
         desiredWorkspaceHorizontalMarginOriginalPx = desiredWorkspaceHorizontalMarginPx;
@@ -312,11 +325,22 @@
         bottomSheetTopPadding = mInsets.top // statusbar height
                 + res.getDimensionPixelSize(R.dimen.bottom_sheet_extra_top_padding)
                 + (isTablet ? 0 : edgeMarginPx); // phones need edgeMarginPx additional padding
+        bottomSheetOpenDuration = res.getInteger(R.integer.config_bottomSheetOpenDuration);
+        bottomSheetCloseDuration = res.getInteger(R.integer.config_bottomSheetCloseDuration);
+        if (isTablet) {
+            bottomSheetWorkspaceScale = workspaceContentScale;
+            // The goal is to set wallpaper to zoom at workspaceContentScale when in AllApps.
+            // When depth is 0, wallpaper zoom is set to maxWallpaperScale.
+            // When depth is 1, wallpaper zoom is set to 1.
+            // For depth to achieve zoom set to maxWallpaperScale * workspaceContentScale:
+            float maxWallpaperScale = res.getFloat(R.dimen.config_wallpaperMaxScale);
+            bottomSheetDepth = Utilities.mapToRange(maxWallpaperScale * workspaceContentScale,
+                    maxWallpaperScale, 1f, 0f, 1f, LINEAR);
+        } else {
+            bottomSheetWorkspaceScale = 1f;
+            bottomSheetDepth = 0f;
+        }
 
-        allAppsTopPadding = isTablet ? bottomSheetTopPadding : 0;
-        allAppsShiftRange = isTablet
-                ? heightPx - allAppsTopPadding
-                : res.getDimensionPixelSize(R.dimen.all_apps_starting_vertical_translate);
         folderLabelTextScale = res.getFloat(R.dimen.folder_label_text_scale);
         folderContentPaddingLeftRight =
                 res.getDimensionPixelSize(R.dimen.folder_content_padding_left_right);
@@ -327,9 +351,8 @@
                 pxFromDp(inv.allAppsBorderSpaces[mTypeIndex].x, mMetrics),
                 pxFromDp(inv.allAppsBorderSpaces[mTypeIndex].y, mMetrics));
         cellLayoutBorderSpaceOriginalPx = new Point(cellLayoutBorderSpacePx);
-        folderCellLayoutBorderSpaceOriginalPx = pxFromDp(inv.folderBorderSpace, mMetrics);
-        folderCellLayoutBorderSpacePx = new Point(folderCellLayoutBorderSpaceOriginalPx,
-                folderCellLayoutBorderSpaceOriginalPx);
+        folderCellLayoutBorderSpacePx = new Point(pxFromDp(inv.folderBorderSpaces.x, mMetrics),
+                pxFromDp(inv.folderBorderSpaces.y, mMetrics));
 
         workspacePageIndicatorHeight = res.getDimensionPixelSize(
                 R.dimen.workspace_page_indicator_height);
@@ -352,14 +375,15 @@
         dropTargetButtonWorkspaceEdgeGapPx = res.getDimensionPixelSize(
                 R.dimen.drop_target_button_workspace_edge_gap);
 
-        workspaceSpringLoadedBottomSpace =
-                res.getDimensionPixelSize(R.dimen.dynamic_grid_min_spring_loaded_space);
         workspaceSpringLoadedMinNextPageVisiblePx = res.getDimensionPixelSize(
                 R.dimen.dynamic_grid_spring_loaded_min_next_space_visible);
 
         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]
@@ -367,29 +391,34 @@
                 && hotseatQsbHeight > 0;
         isQsbInline = inv.inlineQsb[mTypeIndex] && canQsbInline;
 
-        // We shrink hotseat sizes regardless of orientation, if nav buttons are inline and QSB
-        // might be inline in either orientations, to keep hotseat size consistent across rotation.
         areNavButtonsInline = isTaskbarPresent && !isGestureMode;
-        if (areNavButtonsInline && canQsbInline) {
-            numShownHotseatIcons = inv.numShrunkenHotseatIcons;
-        } else {
-            numShownHotseatIcons =
-                    isTwoPanels ? inv.numDatabaseHotseatIcons : inv.numShownHotseatIcons;
-        }
+        numShownHotseatIcons =
+                isTwoPanels ? inv.numDatabaseHotseatIcons : inv.numShownHotseatIcons;
 
         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,37 +427,35 @@
                 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));
+        if (areNavButtonsInline && !isPhone) {
+            /*
+             * 3 nav buttons +
+             * Spacing between nav buttons +
+             * Little space at the end for contextual buttons +
+             * Little space between icons and nav buttons
+             */
+            hotseatBarEndOffset = 3 * res.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size)
+                    + 2 * res.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween)
+                    + res.getDimensionPixelSize(inv.inlineNavButtonsEndSpacing)
+                    + res.getDimensionPixelSize(R.dimen.taskbar_hotseat_nav_spacing);
+        } else {
+            hotseatBarEndOffset = 0;
+        }
 
         overviewTaskMarginPx = res.getDimensionPixelSize(R.dimen.overview_task_margin);
-        overviewTaskMarginGridPx = res.getDimensionPixelSize(R.dimen.overview_task_margin_grid);
         overviewTaskIconSizePx = res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_size);
         overviewTaskIconDrawableSizePx =
                 res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_drawable_size);
         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);
+        overviewTaskThumbnailTopMarginPx = overviewTaskIconSizePx + overviewTaskMarginPx;
+        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);
         overviewActionsHeight = res.getDimensionPixelSize(R.dimen.overview_actions_height);
-        // Grid task's top margin is only overviewTaskIconSizePx + overviewTaskMarginGridPx, but
-        // overviewTaskThumbnailTopMarginPx is applied to all TaskThumbnailView, so exclude the
-        // extra  margin when calculating row spacing.
-        int extraTopMargin = overviewTaskThumbnailTopMarginPx - overviewTaskIconSizePx
-                - overviewTaskMarginGridPx;
-        overviewRowSpacing = res.getDimensionPixelSize(R.dimen.overview_grid_row_spacing)
-                - extraTopMargin;
+        overviewRowSpacing = res.getDimensionPixelSize(R.dimen.overview_grid_row_spacing);
         overviewGridSideMargin = res.getDimensionPixelSize(R.dimen.overview_grid_side_margin);
 
         // Calculate all of the remaining variables.
@@ -446,42 +473,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 =
@@ -492,24 +483,49 @@
         updateWorkspacePadding();
 
         // Hotseat and QSB width depends on updated cellSize and workspace padding
-        hotseatBorderSpace = calculateHotseatBorderSpace();
-        qsbWidth = calculateQsbWidth();
+        recalculateHotseatWidthAndBorderSpace(res);
+
+        // AllApps height calculation depends on updated cellSize
+        if (isTablet) {
+            int collapseHandleHeight =
+                    res.getDimensionPixelOffset(R.dimen.bottom_sheet_handle_area_height);
+            int contentHeight = heightPx - collapseHandleHeight - hotseatQsbHeight;
+            int targetContentHeight = (int) (allAppsCellHeightPx * ALL_APPS_TABLET_MAX_ROWS);
+            allAppsTopPadding = Math.max(mInsets.top, contentHeight - targetContentHeight);
+            allAppsShiftRange = heightPx - allAppsTopPadding;
+        } else {
+            allAppsTopPadding = 0;
+            allAppsShiftRange =
+                    res.getDimensionPixelSize(R.dimen.all_apps_starting_vertical_translate);
+        }
+        allAppsOpenDuration = res.getInteger(R.integer.config_allAppsOpenDuration);
+        allAppsCloseDuration = res.getInteger(R.integer.config_allAppsCloseDuration);
 
         flingToDeleteThresholdVelocity = res.getDimensionPixelSize(
                 R.dimen.drag_flingToDeleteMinVelocity);
 
+        mViewScaleProvider = viewScaleProvider;
+
         // This is done last, after iconSizePx is calculated above.
-        Path dotPath = GraphicsUtils.getShapePath(DEFAULT_DOT_SIZE);
-        mDotRendererWorkSpace = new DotRenderer(iconSizePx, dotPath, DEFAULT_DOT_SIZE);
-        mDotRendererAllApps = iconSizePx == allAppsIconSizePx ? mDotRendererWorkSpace :
-                new DotRenderer(allAppsIconSizePx, dotPath, DEFAULT_DOT_SIZE);
+        mDotRendererWorkSpace = createDotRenderer(iconSizePx, dotRendererCache);
+        mDotRendererAllApps = createDotRenderer(allAppsIconSizePx, dotRendererCache);
+    }
+
+    private static DotRenderer createDotRenderer(
+            int size, @NonNull SparseArray<DotRenderer> cache) {
+        DotRenderer renderer = cache.get(size);
+        if (renderer == null) {
+            renderer = new DotRenderer(size, getShapePath(DEFAULT_DOT_SIZE), DEFAULT_DOT_SIZE);
+            cache.put(size, renderer);
+        }
+        return renderer;
     }
 
     /**
      * QSB width is always calculated because when in 3 button nav the width doesn't follow the
      * width of the hotseat.
      */
-    private int calculateQsbWidth() {
+    private int calculateQsbWidth(int hotseatBorderSpace) {
         if (isQsbInline) {
             int columns = getPanelCount() * inv.numColumns;
             return getIconToIconWidthForColumns(columns)
@@ -524,7 +540,7 @@
     private int getIconToIconWidthForColumns(int columns) {
         return columns * getCellSize().x
                 + (columns - 1) * cellLayoutBorderSpacePx.x
-                - (getCellSize().x - iconSizePx);  // left and right cell space
+                - getCellHorizontalSpace();
     }
 
     private int getHorizontalMarginPx(InvariantDeviceProfile idp, Resources res) {
@@ -537,22 +553,91 @@
                 : 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 void recalculateHotseatWidthAndBorderSpace(Resources res) {
+        hotseatBorderSpace = calculateHotseatBorderSpace();
+        hotseatQsbWidth = calculateQsbWidth(hotseatBorderSpace);
+        // Spaces should be correct when there nav buttons are not inline
+        if (!areNavButtonsInline) {
+            return;
+        }
+
+        // Get the maximum width that the hotseat can be
+        int columns = getPanelCount() * inv.numColumns;
+        int maxHotseatWidth = getIconToIconWidthForColumns(columns);
+        int sideSpace = (availableWidthPx - maxHotseatWidth) / 2;
+        int inlineButtonsOverlap = Math.max(0, hotseatBarEndOffset - sideSpace);
+        // decrease how much the nav buttons go "inside" the hotseat
+        maxHotseatWidth -= inlineButtonsOverlap;
+
+        // Get how much space is required to show the hotseat with QSB
+        int requiredWidth = getHotseatRequiredWidth();
+
+        // If spaces are fine, use them
+        if (requiredWidth <= maxHotseatWidth) {
+            return;
+        }
+
+        // Calculate the difference of widths and remove a little from each space between icons
+        // and QSB if it's inline
+        int spaceDiff = requiredWidth - maxHotseatWidth;
+        int numOfSpaces = numShownHotseatIcons - (isQsbInline ? 0 : 1);
+        hotseatBorderSpace -= (spaceDiff / numOfSpaces);
+
+        int minHotseatIconSpaceDp = res.getDimensionPixelSize(R.dimen.min_hotseat_icon_space);
+        int minHotseatQsbWidthDp = res.getDimensionPixelSize(R.dimen.min_hotseat_qsb_width);
+
+        if (hotseatBorderSpace >= minHotseatIconSpaceDp) {
+            return;
+        }
+
+        // Border space can't be less than the minimum
+        hotseatBorderSpace = minHotseatIconSpaceDp;
+        requiredWidth = getHotseatRequiredWidth();
+
+        // If there is an inline qsb, change its size
+        if (isQsbInline) {
+            hotseatQsbWidth -= requiredWidth - maxHotseatWidth;
+            if (hotseatQsbWidth >= minHotseatQsbWidthDp) {
+                return;
+            }
+
+            // QSB can't be less than the minimum
+            hotseatQsbWidth = minHotseatQsbWidthDp;
+        }
+
+        // If it still doesn't fit, start removing icons
+        do {
+            numShownHotseatIcons--;
+            requiredWidth = getHotseatRequiredWidth();
+        } while (requiredWidth > maxHotseatWidth && numShownHotseatIcons > 1);
+
+        // Add back some space between the icons
+        spaceDiff = maxHotseatWidth - requiredWidth;
+        numOfSpaces = numShownHotseatIcons - (isQsbInline ? 0 : 1);
+        hotseatBorderSpace += (spaceDiff / numOfSpaces);
+    }
+
     private Point getCellLayoutBorderSpace(InvariantDeviceProfile idp) {
         return getCellLayoutBorderSpace(idp, 1f);
-
     }
 
     private Point getCellLayoutBorderSpace(InvariantDeviceProfile idp, float scale) {
@@ -591,10 +676,16 @@
                 widthPx, heightPx, availableWidthPx, availableHeightPx, rotationHint);
         bounds.bounds.offsetTo(windowX, windowY);
         bounds.insets.set(mInsets);
+
+        SparseArray<DotRenderer> dotRendererCache = new SparseArray<>();
+        dotRendererCache.put(iconSizePx, mDotRendererWorkSpace);
+        dotRendererCache.put(allAppsIconSizePx, mDotRendererAllApps);
+
         return new Builder(context, inv, mInfo)
                 .setWindowBounds(bounds)
                 .setUseTwoPanels(isTwoPanels)
                 .setMultiWindowMode(isMultiWindowMode)
+                .setDotRendererCache(dotRendererCache)
                 .setGestureMode(isGestureMode);
     }
 
@@ -611,13 +702,18 @@
                 .setMultiWindowMode(true)
                 .build();
 
-        profile.hideWorkspaceLabelsIfNotEnoughSpace();
-
         // We use these scales to measure and layout the widgets using their full invariant profile
         // sizes and then draw them scaled and centered to fit in their multi-window mode cellspans.
         float appWidgetScaleX = (float) profile.getCellSize().x / getCellSize().x;
         float appWidgetScaleY = (float) profile.getCellSize().y / getCellSize().y;
-        profile.appWidgetScale.set(appWidgetScaleX, appWidgetScaleY);
+        if (appWidgetScaleX != 1 || appWidgetScaleY != 1) {
+            final PointF p = new PointF(appWidgetScaleX, appWidgetScaleY);
+            profile = profile.toBuilder(context)
+                    .setViewScaleProvider(i -> p)
+                    .build();
+        }
+
+        profile.hideWorkspaceLabelsIfNotEnoughSpace();
 
         return profile;
     }
@@ -764,7 +860,7 @@
         // All apps
         updateAllAppsIconSize(scale, res);
 
-        updateHotseatIconSize(iconSizePx);
+        updateHotseatSizes(iconSizePx);
 
         // Folder icon
         folderIconSizePx = IconNormalizer.getNormalizedCircleSize(iconSizePx);
@@ -777,15 +873,10 @@
      */
     private int calculateHotseatBorderSpace() {
         if (!isScalableGrid) return 0;
-        //TODO(http://b/228998082) remove this when 3 button spaces are fixed
-        if (areNavButtonsInline) {
-            return pxFromDp(inv.hotseatBorderSpaces[mTypeIndex], mMetrics);
-        } else {
-            int columns = inv.hotseatColumnSpan[mTypeIndex];
-            float hotseatWidthPx = getIconToIconWidthForColumns(columns);
-            float hotseatIconsTotalPx = iconSizePx * numShownHotseatIcons;
-            return (int) (hotseatWidthPx - hotseatIconsTotalPx) / (numShownHotseatIcons - 1);
-        }
+        int columns = inv.hotseatColumnSpan[mTypeIndex];
+        float hotseatWidthPx = getIconToIconWidthForColumns(columns);
+        float hotseatIconsTotalPx = iconSizePx * numShownHotseatIcons;
+        return (int) (hotseatWidthPx - hotseatIconsTotalPx) / (numShownHotseatIcons - 1);
     }
 
 
@@ -802,13 +893,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 +907,7 @@
             allAppsIconTextSizePx = (int) (pxFromSp(invIconTextSizeSp, mMetrics) * scale);
             allAppsIconDrawablePaddingPx =
                     res.getDimensionPixelSize(R.dimen.all_apps_icon_drawable_padding);
+            allAppsCellWidthPx = allAppsIconSizePx + (2 * allAppsIconDrawablePaddingPx);
         }
 
         updateAllAppsContainerWidth(res);
@@ -865,16 +957,14 @@
         int textHeight = Utilities.calculateTextHeight(folderChildTextSizePx);
 
         if (isScalableGrid) {
-            int minWidth = folderChildIconSizePx + iconDrawablePaddingPx * 2;
-            int minHeight = folderChildIconSizePx + iconDrawablePaddingPx * 2 + textHeight;
+            folderCellWidthPx = pxFromDp(inv.folderCellSize.x, mMetrics, scale);
+            folderCellHeightPx = pxFromDp(inv.folderCellSize.y, mMetrics, scale);
 
-            folderCellWidthPx = (int) Math.max(minWidth, cellWidthPx * scale);
-            folderCellHeightPx = (int) Math.max(minHeight, cellHeightPx * scale);
-
-            int scaledSpace = (int) (folderCellLayoutBorderSpaceOriginalPx * scale);
-            folderCellLayoutBorderSpacePx = new Point(scaledSpace, scaledSpace);
-            folderContentPaddingLeftRight = scaledSpace;
-            folderContentPaddingTop = scaledSpace;
+            folderCellLayoutBorderSpacePx = new Point(
+                    pxFromDp(inv.folderBorderSpaces.x, mMetrics, scale),
+                    pxFromDp(inv.folderBorderSpaces.y, mMetrics, scale));
+            folderContentPaddingLeftRight = folderCellLayoutBorderSpacePx.x;
+            folderContentPaddingTop = pxFromDp(inv.folderTopPadding, mMetrics, scale);
         } else {
             int cellPaddingX = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_x_padding)
                     * scale);
@@ -922,6 +1012,13 @@
     }
 
     /**
+     * Returns the left and right space on the cell, which is the cell width - icon size
+     */
+    public int getCellHorizontalSpace() {
+        return getCellSize().x - iconSizePx;
+    }
+
+    /**
      * Gets the number of panels within the workspace.
      */
     public int getPanelCount() {
@@ -932,46 +1029,42 @@
      * 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(Context context) {
+        Rect hotseatBarPadding = getHotseatLayoutPadding(context);
         int cellHeight = calculateCellHeight(
-                heightPx - mHotseatPadding.top - mHotseatPadding.bottom, hotseatBorderSpace,
+                heightPx - hotseatBarPadding.top - hotseatBarPadding.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 + hotseatBarPadding.bottom;
     }
 
     /**
      * Gets the scaled top of the workspace in px for the spring-loaded edit state.
      */
     public float getCellLayoutSpringLoadShrunkTop() {
-        workspaceSpringLoadShrunkTop = mInsets.top + dropTargetBarTopMarginPx + dropTargetBarSizePx
+        return mInsets.top + dropTargetBarTopMarginPx + dropTargetBarSizePx
                 + dropTargetBarBottomMarginPx;
-        return workspaceSpringLoadShrunkTop;
     }
 
     /**
      * Gets the scaled bottom of the workspace in px for the spring-loaded edit state.
      */
-    private float getCellLayoutSpringLoadShrunkBottom() {
+    public float getCellLayoutSpringLoadShrunkBottom(Context context) {
         int topOfHotseat = hotseatBarSizePx + springLoadedHotseatBarTopMarginPx;
-        workspaceSpringLoadShrunkBottom =
-                heightPx - (isVerticalBarLayout() ? getVerticalHotseatLastItemBottomOffset()
-                        : topOfHotseat);
-        return workspaceSpringLoadShrunkBottom;
+        return heightPx - (isVerticalBarLayout()
+                ? getVerticalHotseatLastItemBottomOffset(context) : topOfHotseat);
     }
 
     /**
      * Gets the scale of the workspace for the spring-loaded edit state.
      */
-    public float getWorkspaceSpringLoadScale() {
-        float scale = (getCellLayoutSpringLoadShrunkBottom() - getCellLayoutSpringLoadShrunkTop())
-                / getCellLayoutHeight();
+    public float getWorkspaceSpringLoadScale(Context context) {
+        float scale =
+                (getCellLayoutSpringLoadShrunkBottom(context) - getCellLayoutSpringLoadShrunkTop())
+                        / getCellLayoutHeight();
         scale = Math.min(scale, 1f);
 
-        // Reduce scale if next pages would not be visible after scaling the workspace
+        // Reduce scale if next pages would not be visible after scaling the workspace.
         int workspaceWidth = availableWidthPx;
         float scaledWorkspaceWidth = workspaceWidth * scale;
         float maxAvailableWidth = workspaceWidth - (2 * workspaceSpringLoadedMinNextPageVisiblePx);
@@ -1022,10 +1115,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;
 
@@ -1052,6 +1146,7 @@
      * Returns the padding for hotseat view
      */
     public Rect getHotseatLayoutPadding(Context context) {
+        Rect hotseatBarPadding = new Rect();
         if (isVerticalBarLayout()) {
             // The hotseat icons will be placed in the middle of the hotseat cells.
             // Changing the hotseatCellHeightPx is not affecting hotseat icon positions
@@ -1065,50 +1160,46 @@
                     + diffOverlapFactor), 0);
 
             if (isSeascape()) {
-                mHotseatPadding.set(mInsets.left + hotseatBarSidePaddingStartPx, paddingTop,
+                hotseatBarPadding.set(mInsets.left + hotseatBarSidePaddingStartPx, paddingTop,
                         hotseatBarSidePaddingEndPx, paddingBottom);
             } else {
-                mHotseatPadding.set(hotseatBarSidePaddingEndPx, paddingTop,
+                hotseatBarPadding.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;
-            int requiredWidth = iconSizePx * numShownHotseatIcons
-                    + hotseatBorderSpace * (numShownHotseatIcons - 1)
-                    + additionalQsbSpace;
-            int endOffset = ApiWrapper.getHotseatEndOffset(context);
-            int hotseatWidth = Math.min(requiredWidth, availableWidthPx - endOffset);
+            int requiredWidth = getHotseatRequiredWidth();
+            int hotseatWidth = Math.min(requiredWidth, availableWidthPx - hotseatBarEndOffset);
             int sideSpacing = (availableWidthPx - hotseatWidth) / 2;
 
-            mHotseatPadding.set(sideSpacing, hotseatTopPadding, sideSpacing, hotseatBottomPadding);
+            hotseatBarPadding.set(sideSpacing, hotseatBarTopPadding, sideSpacing,
+                    hotseatBarBottomPadding);
 
             boolean isRtl = Utilities.isRtl(context.getResources());
             if (isRtl) {
-                mHotseatPadding.right += additionalQsbSpace;
+                hotseatBarPadding.right += getAdditionalQsbSpace();
             } else {
-                mHotseatPadding.left += additionalQsbSpace;
+                hotseatBarPadding.left += getAdditionalQsbSpace();
             }
 
-            if (endOffset > sideSpacing) {
+            if (hotseatBarEndOffset > sideSpacing) {
                 int diff = isRtl
-                        ? sideSpacing - endOffset
-                        : endOffset - sideSpacing;
-                mHotseatPadding.left -= diff;
-                mHotseatPadding.right += diff;
+                        ? sideSpacing - hotseatBarEndOffset
+                        : hotseatBarEndOffset - sideSpacing;
+                hotseatBarPadding.left -= diff;
+                hotseatBarPadding.right += diff;
             }
         } else if (isScalableGrid) {
-            int sideSpacing = (availableWidthPx - qsbWidth) / 2;
-            mHotseatPadding.set(sideSpacing,
-                    hotseatBarTopPaddingPx,
+            int sideSpacing = (availableWidthPx - hotseatQsbWidth) / 2;
+            hotseatBarPadding.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 +1208,29 @@
             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,
+            hotseatBarPadding.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 hotseatBarPadding;
+    }
+
+    private int getAdditionalQsbSpace() {
+        return isQsbInline ? hotseatQsbWidth + hotseatBorderSpace : 0;
+    }
+
+    /**
+     * Calculate how much space the hotseat needs to be shown completely
+     */
+    private int getHotseatRequiredWidth() {
+        int additionalQsbSpace = getAdditionalQsbSpace();
+        return iconSizePx * numShownHotseatIcons
+                + hotseatBorderSpace * (numShownHotseatIcons - 1)
+                + additionalQsbSpace;
     }
 
     /**
@@ -1132,27 +1238,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 +1264,7 @@
         int taskbarIconBottomSpace = (taskbarSize - iconSizePx) / 2;
         int launcherIconBottomSpace =
                 Math.min((hotseatCellHeightPx - iconSizePx) / 2, gridVisualizationPaddingY);
-        return getHotseatBottomPadding() + launcherIconBottomSpace - taskbarIconBottomSpace;
+        return getHotseatBarBottomPadding() + launcherIconBottomSpace - taskbarIconBottomSpace;
     }
 
     /**
@@ -1172,7 +1273,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;
@@ -1185,6 +1286,19 @@
     }
 
     /**
+     * Takes the View and return the scales of width and height depending on the DeviceProfile
+     * specifications
+     *
+     * @param itemInfo The tag of the widget view
+     * @return A PointF instance with the x set to be the scale of width, and y being the scale of
+     * height
+     */
+    @NonNull
+    public PointF getAppWidgetScale(@Nullable final ItemInfo itemInfo) {
+        return mViewScaleProvider.getScaleFromItemInfo(itemInfo);
+    }
+
+    /**
      * @return the bounds for which the open folders should be contained within
      */
     public Rect getAbsoluteOpenFolderBounds() {
@@ -1267,7 +1381,12 @@
         return "\t" + name + ": " + value + "px (" + dpiFromPx(value, mMetrics.densityDpi) + "dp)";
     }
 
-    public void dump(String prefix, PrintWriter writer) {
+    private String dpPointFToString(String name, PointF value) {
+        return String.format(Locale.ENGLISH, "\t%s: PointF(%.1f, %.1f)dp", name, value.x, value.y);
+    }
+
+    /** Dumps various DeviceProfile variables to the specified writer. */
+    public void dump(Context context, String prefix, PrintWriter writer) {
         writer.println(prefix + "DeviceProfile:");
         writer.println(prefix + "\t1 dp = " + mMetrics.density + " px");
 
@@ -1301,7 +1420,7 @@
         writer.println(prefix + "\tinv.numSearchContainerColumns: "
                 + inv.numSearchContainerColumns);
 
-        writer.println(prefix + "\tminCellSize: " + inv.minCellSize[mTypeIndex] + "dp");
+        writer.println(prefix + dpPointFToString("minCellSize", inv.minCellSize[mTypeIndex]));
 
         writer.println(prefix + pxToDpStr("cellWidthPx", cellWidthPx));
         writer.println(prefix + pxToDpStr("cellHeightPx", cellHeightPx));
@@ -1313,9 +1432,12 @@
                 cellLayoutBorderSpacePx.x));
         writer.println(prefix + pxToDpStr("cellLayoutBorderSpacePx Vertical",
                 cellLayoutBorderSpacePx.y));
-        writer.println(prefix + pxToDpStr("cellLayoutPaddingPx.left", cellLayoutPaddingPx.left));
-        writer.println(prefix + pxToDpStr("cellLayoutPaddingPx.top", cellLayoutPaddingPx.top));
-        writer.println(prefix + pxToDpStr("cellLayoutPaddingPx.right", cellLayoutPaddingPx.right));
+        writer.println(
+                prefix + pxToDpStr("cellLayoutPaddingPx.left", cellLayoutPaddingPx.left));
+        writer.println(
+                prefix + pxToDpStr("cellLayoutPaddingPx.top", cellLayoutPaddingPx.top));
+        writer.println(
+                prefix + pxToDpStr("cellLayoutPaddingPx.right", cellLayoutPaddingPx.right));
         writer.println(
                 prefix + pxToDpStr("cellLayoutPaddingPx.bottom", cellLayoutPaddingPx.bottom));
 
@@ -1329,24 +1451,32 @@
         writer.println(prefix + pxToDpStr("folderChildTextSizePx", folderChildTextSizePx));
         writer.println(prefix + pxToDpStr("folderChildDrawablePaddingPx",
                 folderChildDrawablePaddingPx));
-        writer.println(prefix + pxToDpStr("folderCellLayoutBorderSpaceOriginalPx",
-                folderCellLayoutBorderSpaceOriginalPx));
         writer.println(prefix + pxToDpStr("folderCellLayoutBorderSpacePx Horizontal",
                 folderCellLayoutBorderSpacePx.x));
         writer.println(prefix + pxToDpStr("folderCellLayoutBorderSpacePx Vertical",
                 folderCellLayoutBorderSpacePx.y));
+        writer.println(prefix + pxToDpStr("folderContentPaddingLeftRight",
+                folderContentPaddingLeftRight));
+        writer.println(prefix + pxToDpStr("folderTopPadding", folderContentPaddingTop));
 
         writer.println(prefix + pxToDpStr("bottomSheetTopPadding", bottomSheetTopPadding));
+        writer.println(prefix + "\tbottomSheetOpenDuration: " + bottomSheetOpenDuration);
+        writer.println(prefix + "\tbottomSheetCloseDuration: " + bottomSheetCloseDuration);
+        writer.println(prefix + "\tbottomSheetWorkspaceScale: " + bottomSheetWorkspaceScale);
+        writer.println(prefix + "\tbottomSheetDepth: " + bottomSheetDepth);
 
         writer.println(prefix + pxToDpStr("allAppsShiftRange", allAppsShiftRange));
         writer.println(prefix + pxToDpStr("allAppsTopPadding", allAppsTopPadding));
+        writer.println(prefix + "\tallAppsOpenDuration: " + allAppsOpenDuration);
+        writer.println(prefix + "\tallAppsCloseDuration: " + allAppsCloseDuration);
         writer.println(prefix + pxToDpStr("allAppsIconSizePx", allAppsIconSizePx));
         writer.println(prefix + pxToDpStr("allAppsIconTextSizePx", allAppsIconTextSizePx));
         writer.println(prefix + pxToDpStr("allAppsIconDrawablePaddingPx",
                 allAppsIconDrawablePaddingPx));
         writer.println(prefix + pxToDpStr("allAppsCellHeightPx", allAppsCellHeightPx));
         writer.println(prefix + pxToDpStr("allAppsCellWidthPx", allAppsCellWidthPx));
-        writer.println(prefix + pxToDpStr("allAppsBorderSpacePx", allAppsBorderSpacePx.x));
+        writer.println(prefix + pxToDpStr("allAppsBorderSpacePxX", allAppsBorderSpacePx.x));
+        writer.println(prefix + pxToDpStr("allAppsBorderSpacePxY", allAppsBorderSpacePx.y));
         writer.println(prefix + "\tnumShownAllAppsColumns: " + numShownAllAppsColumns);
         writer.println(prefix + pxToDpStr("allAppsLeftRightPadding", allAppsLeftRightPadding));
         writer.println(prefix + pxToDpStr("allAppsLeftRightMargin", allAppsLeftRightMargin));
@@ -1354,22 +1484,29 @@
         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("hotseatBarBottomSpacePx", hotseatBarBottomSpacePx));
         writer.println(prefix + pxToDpStr("hotseatBarSidePaddingStartPx",
                 hotseatBarSidePaddingStartPx));
         writer.println(prefix + pxToDpStr("hotseatBarSidePaddingEndPx",
                 hotseatBarSidePaddingEndPx));
+        writer.println(prefix + pxToDpStr("hotseatBarEndOffset", hotseatBarEndOffset));
+        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));
+        Rect hotseatLayoutPadding = getHotseatLayoutPadding(context);
+        writer.println(prefix + pxToDpStr("getHotseatLayoutPadding(context).top",
+                hotseatLayoutPadding.top));
+        writer.println(prefix + pxToDpStr("getHotseatLayoutPadding(context).bottom",
+                hotseatLayoutPadding.bottom));
+        writer.println(prefix + pxToDpStr("getHotseatLayoutPadding(context).left",
+                hotseatLayoutPadding.left));
+        writer.println(prefix + pxToDpStr("getHotseatLayoutPadding(context).right",
+                hotseatLayoutPadding.right));
         writer.println(prefix + "\tnumShownHotseatIcons: " + numShownHotseatIcons);
         writer.println(prefix + pxToDpStr("hotseatBorderSpace", hotseatBorderSpace));
         writer.println(prefix + "\tisQsbInline: " + isQsbInline);
-        writer.println(prefix + pxToDpStr("qsbWidth", qsbWidth));
+        writer.println(prefix + pxToDpStr("hotseatQsbWidth", hotseatQsbWidth));
 
         writer.println(prefix + "\tisTaskbarPresent:" + isTaskbarPresent);
         writer.println(prefix + "\tisTaskbarPresentInApps:" + isTaskbarPresentInApps);
@@ -1394,10 +1531,8 @@
         }
         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));
         writer.println(prefix + pxToDpStr("overviewTaskIconSizePx", overviewTaskIconSizePx));
         writer.println(prefix + pxToDpStr("overviewTaskIconDrawableSizePx",
                 overviewTaskIconDrawableSizePx));
@@ -1420,16 +1555,16 @@
         writer.println(
                 prefix + pxToDpStr("dropTargetBarBottomMarginPx", dropTargetBarBottomMarginPx));
 
-        writer.println(
-                prefix + pxToDpStr("workspaceSpringLoadShrunkTop", workspaceSpringLoadShrunkTop));
-        writer.println(prefix + pxToDpStr("workspaceSpringLoadShrunkBottom",
-                workspaceSpringLoadShrunkBottom));
-        writer.println(prefix + pxToDpStr("workspaceSpringLoadedBottomSpace",
-                workspaceSpringLoadedBottomSpace));
+        writer.println(prefix + pxToDpStr("getCellLayoutSpringLoadShrunkTop()",
+                getCellLayoutSpringLoadShrunkTop()));
+        writer.println(prefix + pxToDpStr("getCellLayoutSpringLoadShrunkBottom()",
+                getCellLayoutSpringLoadShrunkBottom(context)));
         writer.println(prefix + pxToDpStr("workspaceSpringLoadedMinNextPageVisiblePx",
                 workspaceSpringLoadedMinNextPageVisiblePx));
-        writer.println(
-                prefix + pxToDpStr("getWorkspaceSpringLoadScale()", getWorkspaceSpringLoadScale()));
+        writer.println(prefix + pxToDpStr("getWorkspaceSpringLoadScale()",
+                getWorkspaceSpringLoadScale(context)));
+        writer.println(prefix + pxToDpStr("getCellLayoutHeight()", getCellLayoutHeight()));
+        writer.println(prefix + pxToDpStr("getCellLayoutWidth()", getCellLayoutWidth()));
     }
 
     private static Context getContext(Context c, Info info, int orientation, WindowBounds bounds) {
@@ -1483,6 +1618,22 @@
         }
     }
 
+    /**
+     * Handler that deals with ItemInfo of the views for the DeviceProfile
+     */
+    @FunctionalInterface
+    public interface ViewScaleProvider {
+        /**
+         * Get the scales from the view
+         *
+         * @param itemInfo The tag of the widget view
+         * @return PointF instance containing the scale information, or null if using the default
+         * app widget scale of this device profile.
+         */
+        @NonNull
+        PointF getScaleFromItemInfo(@Nullable ItemInfo itemInfo);
+    }
+
     public static class Builder {
         private Context mContext;
         private InvariantDeviceProfile mInv;
@@ -1494,6 +1645,9 @@
         private boolean mIsMultiWindowMode = false;
         private Boolean mTransposeLayoutWithOrientation;
         private Boolean mIsGestureMode;
+        private ViewScaleProvider mViewScaleProvider = null;
+
+        private SparseArray<DotRenderer> mDotRendererCache;
 
         public Builder(Context context, InvariantDeviceProfile inv, Info info) {
             mContext = context;
@@ -1511,6 +1665,10 @@
             return this;
         }
 
+        public Builder setDotRendererCache(SparseArray<DotRenderer> dotRendererCache) {
+            mDotRendererCache = dotRendererCache;
+            return this;
+        }
 
         public Builder setWindowBounds(WindowBounds bounds) {
             mWindowBounds = bounds;
@@ -1527,6 +1685,19 @@
             return this;
         }
 
+        /**
+         * Set the viewScaleProvider for the builder
+         *
+         * @param viewScaleProvider The viewScaleProvider to be set for the
+         *                                DeviceProfile
+         * @return This builder
+         */
+        @NonNull
+        public Builder setViewScaleProvider(@Nullable ViewScaleProvider viewScaleProvider) {
+            mViewScaleProvider = viewScaleProvider;
+            return this;
+        }
+
         public DeviceProfile build() {
             if (mWindowBounds == null) {
                 throw new IllegalArgumentException("Window bounds not set");
@@ -1535,10 +1706,17 @@
                 mTransposeLayoutWithOrientation = !mInfo.isTablet(mWindowBounds);
             }
             if (mIsGestureMode == null) {
-                mIsGestureMode = DisplayController.getNavigationMode(mContext).hasGestures;
+                mIsGestureMode = mInfo.navigationMode.hasGestures;
             }
-            return new DeviceProfile(mContext, mInv, mInfo, mWindowBounds, mIsMultiWindowMode,
-                    mTransposeLayoutWithOrientation, mUseTwoPanels, mIsGestureMode);
+            if (mDotRendererCache == null) {
+                mDotRendererCache = new SparseArray<>();
+            }
+            if (mViewScaleProvider == null) {
+                mViewScaleProvider = DEFAULT_PROVIDER;
+            }
+            return new DeviceProfile(mContext, mInv, mInfo, mWindowBounds, mDotRendererCache,
+                    mIsMultiWindowMode, mTransposeLayoutWithOrientation, mUseTwoPanels,
+                    mIsGestureMode, mViewScaleProvider);
         }
     }
 
diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java
index 70d8476..2d99510 100644
--- a/src/com/android/launcher3/DropTarget.java
+++ b/src/com/android/launcher3/DropTarget.java
@@ -20,7 +20,6 @@
 import android.graphics.Rect;
 
 import com.android.launcher3.accessibility.DragViewStateAnnouncer;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.dragndrop.DraggableView;
@@ -82,11 +81,8 @@
         public final InstanceId logInstanceId = new InstanceIdSequence().newInstanceId();
 
         public DragObject(Context context) {
-            if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
-                Executors.MODEL_EXECUTOR.post(() -> {
-                    folderNameProvider = FolderNameProvider.newInstance(context);
-                });
-            }
+            Executors.MODEL_EXECUTOR.post(() ->
+                    folderNameProvider = FolderNameProvider.newInstance(context));
         }
 
         /**
diff --git a/src/com/android/launcher3/DropTargetBar.java b/src/com/android/launcher3/DropTargetBar.java
index d908440..98ecf3a 100644
--- a/src/com/android/launcher3/DropTargetBar.java
+++ b/src/com/android/launcher3/DropTargetBar.java
@@ -37,7 +37,7 @@
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragController.DragListener;
 import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 
 /*
  * The top bar containing various drop targets: Delete/App Info/Uninstall.
@@ -151,6 +151,8 @@
             int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST);
 
             ButtonDropTarget firstButton = mTempTargets[0];
+            firstButton.setTextSize(TypedValue.COMPLEX_UNIT_PX,
+                    mLauncher.getDeviceProfile().dropTargetTextSizePx);
             firstButton.setTextVisible(true);
             firstButton.setIconVisible(true);
             firstButton.measure(widthSpec, heightSpec);
@@ -160,14 +162,16 @@
             int horizontalPadding = dp.dropTargetHorizontalPaddingPx;
 
             ButtonDropTarget firstButton = mTempTargets[0];
+            firstButton.setTextSize(TypedValue.COMPLEX_UNIT_PX, dp.dropTargetTextSizePx);
             firstButton.setTextVisible(true);
             firstButton.setIconVisible(true);
             firstButton.setTextMultiLine(false);
-            // Reset second button padding in case it was previously changed to multi-line text.
+            // Reset first button padding in case it was previously changed to multi-line text.
             firstButton.setPadding(horizontalPadding, verticalPadding, horizontalPadding,
                     verticalPadding);
 
             ButtonDropTarget secondButton = mTempTargets[1];
+            secondButton.setTextSize(TypedValue.COMPLEX_UNIT_PX, dp.dropTargetTextSizePx);
             secondButton.setTextVisible(true);
             secondButton.setIconVisible(true);
             secondButton.setTextMultiLine(false);
@@ -175,28 +179,23 @@
             secondButton.setPadding(horizontalPadding, verticalPadding, horizontalPadding,
                     verticalPadding);
 
-            float scale = dp.getWorkspaceSpringLoadScale();
-            int scaledPanelWidth = (int) (dp.getCellLayoutWidth() * scale);
-
             int availableWidth;
             if (dp.isTwoPanels) {
-                // Both buttons for two panel fit to the width of one Cell Layout (less
-                // half of the center gap between the buttons).
-                int halfButtonGap = dp.dropTargetGapPx / 2;
-                availableWidth = scaledPanelWidth - halfButtonGap / 2;
+                // Each button for two panel fits to half the width of the screen excluding the
+                // center gap between the buttons.
+                availableWidth = (dp.availableWidthPx - dp.dropTargetGapPx) / 2;
             } else {
-                // Both buttons plus the button gap do not display past the edge of the scaled
-                // workspace, less a pre-defined gap from the edge of the workspace.
-                availableWidth = scaledPanelWidth - dp.dropTargetGapPx
-                        - 2 * dp.dropTargetButtonWorkspaceEdgeGapPx;
+                // Both buttons plus the button gap do not display past the edge of the screen.
+                availableWidth = dp.availableWidthPx - dp.dropTargetGapPx;
             }
 
             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,13 +208,24 @@
             }
             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);
                 }
             }
+
+            // If text is still truncated, shrink to fit in measured width and resize both targets.
+            float minTextSize =
+                    Math.min(firstButton.resizeTextToFit(), secondButton.resizeTextToFit());
+            if (firstButton.getTextSize() != minTextSize
+                    || secondButton.getTextSize() != minTextSize) {
+                firstButton.setTextSize(minTextSize);
+                secondButton.setTextSize(minTextSize);
+            }
         }
         setMeasuredDimension(width, height);
     }
@@ -229,7 +239,7 @@
 
         DeviceProfile dp = mLauncher.getDeviceProfile();
         // Center vertical bar over scaled workspace, accounting for hotseat offset.
-        float scale = dp.getWorkspaceSpringLoadScale();
+        float scale = dp.getWorkspaceSpringLoadScale(mLauncher);
         Workspace<?> ws = mLauncher.getWorkspace();
         int barCenter;
         if (dp.isTwoPanels) {
diff --git a/src/com/android/launcher3/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java
index 4629ca7..11f2020 100644
--- a/src/com/android/launcher3/ExtendedEditText.java
+++ b/src/com/android/launcher3/ExtendedEditText.java
@@ -15,7 +15,7 @@
  */
 package com.android.launcher3;
 
-import static com.android.launcher3.util.UiThreadHelper.hideKeyboardAsync;
+import static com.android.launcher3.logging.KeyboardStateManager.KeyboardState.SHOW;
 
 import android.content.Context;
 import android.text.TextUtils;
@@ -33,8 +33,6 @@
  * Note: AppCompatEditText doesn't fully support #displayCompletions and #onCommitCompletion
  */
 public class ExtendedEditText extends EditText {
-
-    private boolean mShowImeAfterFirstLayout;
     private boolean mForceDisableSuggestions = false;
 
     /**
@@ -85,28 +83,21 @@
         return false;
     }
 
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-        if (mShowImeAfterFirstLayout) {
-            // soft input only shows one frame after the layout of the EditText happens,
-            post(() -> {
-                showSoftInput();
-                mShowImeAfterFirstLayout = false;
-            });
-        }
-    }
-
-
     public void showKeyboard() {
-        mShowImeAfterFirstLayout = !showSoftInput();
+        onKeyboardShown();
+        showSoftInput();
     }
 
     public void hideKeyboard() {
-        hideKeyboardAsync(ActivityContext.lookupContext(getContext()), getWindowToken());
+        ActivityContext.lookupContext(getContext()).hideKeyboard();
         clearFocus();
     }
 
+    protected void onKeyboardShown() {
+        ActivityContext.lookupContext(getContext()).getStatsLogManager()
+                .keyboardStateManager().setKeyboardState(SHOW);
+    }
+
     private boolean showSoftInput() {
         return requestFocus() &&
                 getContext().getSystemService(InputMethodManager.class)
diff --git a/src/com/android/launcher3/FastScrollRecyclerView.java b/src/com/android/launcher3/FastScrollRecyclerView.java
index f117069..2f927d3 100644
--- a/src/com/android/launcher3/FastScrollRecyclerView.java
+++ b/src/com/android/launcher3/FastScrollRecyclerView.java
@@ -56,7 +56,9 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        bindFastScrollbar();
+        if (mScrollbar == null || !mScrollbar.hasRecyclerView()) {
+            bindFastScrollbar();
+        }
     }
 
     public void bindFastScrollbar() {
@@ -86,15 +88,19 @@
      * 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 availableScrollHeight = computeVerticalScrollRange() - 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();
     }
 
     /**
@@ -138,10 +144,7 @@
 
         // IF scroller is at the very top OR there is no scroll bar because there is probably not
         // enough items to scroll, THEN it's okay for the container to be pulled down.
-        if (getCurrentScrollY() == 0) {
-            return true;
-        }
-        return getAdapter() == null || getAdapter().getItemCount() == 0;
+        return computeVerticalScrollOffset() == 0;
     }
 
     /**
@@ -154,14 +157,6 @@
     /**
      * 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();
-
-    /**
-     * Maps the touch (from 0..1) to the adapter position that should be visible.
-     * <p>Override in each subclass of this base class.
      */
     public abstract String scrollToPositionAtProgress(float touchFraction);
 
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 76106fc..bf492a9 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -47,7 +47,6 @@
     private Consumer<Boolean> mOnVisibilityAggregatedCallback;
 
     private final View mQsb;
-    private final int mQsbHeight;
 
     public Hotseat(Context context) {
         this(context, null);
@@ -62,8 +61,6 @@
 
         mQsb = LayoutInflater.from(context).inflate(R.layout.search_container_hotseat, this, false);
         addView(mQsb);
-
-        mQsbHeight = getResources().getDimensionPixelSize(R.dimen.qsb_widget_height);
     }
 
     /**
@@ -111,9 +108,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());
@@ -173,29 +168,29 @@
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 
-        int qsbWidth = mActivity.getDeviceProfile().qsbWidth;
-
-        mQsb.measure(MeasureSpec.makeMeasureSpec(qsbWidth, MeasureSpec.EXACTLY),
-                MeasureSpec.makeMeasureSpec(mQsbHeight, MeasureSpec.EXACTLY));
+        DeviceProfile dp = mActivity.getDeviceProfile();
+        mQsb.measure(MeasureSpec.makeMeasureSpec(dp.hotseatQsbWidth, MeasureSpec.EXACTLY),
+                MeasureSpec.makeMeasureSpec(dp.hotseatQsbHeight, MeasureSpec.EXACTLY));
     }
 
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
 
-        int qsbWidth = mQsb.getMeasuredWidth();
+        int qsbMeasuredWidth = mQsb.getMeasuredWidth();
         int left;
-        if (mActivity.getDeviceProfile().isQsbInline) {
-            int qsbSpace = mActivity.getDeviceProfile().hotseatBorderSpace;
+        DeviceProfile dp = mActivity.getDeviceProfile();
+        if (dp.isQsbInline) {
+            int qsbSpace = dp.hotseatBorderSpace;
             left = Utilities.isRtl(getResources()) ? r - getPaddingRight() + qsbSpace
-                    : l + getPaddingLeft() - qsbWidth - qsbSpace;
+                    : l + getPaddingLeft() - qsbMeasuredWidth - qsbSpace;
         } else {
-            left = (r - l - qsbWidth) / 2;
+            left = (r - l - qsbMeasuredWidth) / 2;
         }
-        int right = left + qsbWidth;
+        int right = left + qsbMeasuredWidth;
 
-        int bottom = b - t - mActivity.getDeviceProfile().getQsbOffsetY();
-        int top = bottom - mQsbHeight;
+        int bottom = b - t - dp.getQsbOffsetY();
+        int top = bottom - dp.hotseatQsbHeight;
         mQsb.layout(left, top, right, bottom);
     }
 
@@ -206,6 +201,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..6a262c3 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -46,9 +46,12 @@
 import androidx.annotation.IntDef;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
+import androidx.core.content.res.ResourcesCompat;
 
+import com.android.launcher3.icons.DotRenderer;
 import com.android.launcher3.model.DeviceGridState;
 import com.android.launcher3.provider.RestoreDbTask;
+import com.android.launcher3.testing.shared.ResourceUtils;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.Info;
 import com.android.launcher3.util.IntArray;
@@ -61,8 +64,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 +80,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;
@@ -125,8 +127,11 @@
     public PointF[] minCellSize;
 
     public PointF[] borderSpaces;
-    public float folderBorderSpace;
-    public float[] hotseatBorderSpaces;
+    public int inlineNavButtonsEndSpacing;
+
+    public PointF folderBorderSpaces;
+    public PointF folderCellSize;
+    public float folderTopPadding;
 
     public float[] horizontalMargin;
 
@@ -143,11 +148,6 @@
     public int numShownHotseatIcons;
 
     /**
-     * Number of icons inside the hotseat area when using 3 buttons navigation.
-     */
-    public int numShrunkenHotseatIcons;
-
-    /**
      * Number of icons inside the hotseat area that is stored in the database. This is greater than
      * or equal to numnShownHotseatIcons, allowing for a seamless transition between two hotseat
      * sizes that share the same DB.
@@ -155,6 +155,8 @@
     public int numDatabaseHotseatIcons;
 
     public int[] hotseatColumnSpan;
+    public float[] hotseatBarBottomSpace;
+    public float[] hotseatQsbSpace;
 
     /**
      * Number of columns in the all apps list.
@@ -171,7 +173,7 @@
     public String dbFile;
     public int defaultLayoutId;
     int demoModeLayoutId;
-    boolean[] inlineQsb = new boolean[COUNT_SIZES];
+    public boolean[] inlineQsb = new boolean[COUNT_SIZES];
 
     /**
      * An immutable list of supported profiles.
@@ -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,
@@ -255,8 +258,6 @@
                 COUNT_SIZES);
         System.arraycopy(defaultDisplayOption.borderSpaces, 0, result.borderSpaces, 0,
                 COUNT_SIZES);
-        System.arraycopy(defaultDisplayOption.inlineQsb, 0, result.inlineQsb, 0,
-                COUNT_SIZES);
 
         initGrid(context, myInfo, result, deviceType);
     }
@@ -336,6 +337,8 @@
         devicePaddingId = closestProfile.devicePaddingId;
         this.deviceType = deviceType;
 
+        inlineNavButtonsEndSpacing = closestProfile.inlineNavButtonsEndSpacing;
+
         mExtraAttrs = closestProfile.extraAttrs;
 
         iconSize = displayOption.iconSizes;
@@ -351,16 +354,19 @@
         minCellSize = displayOption.minCellSize;
 
         borderSpaces = displayOption.borderSpaces;
-        folderBorderSpace = displayOption.folderBorderSpace;
+
+        folderBorderSpaces = displayOption.folderBorderSpaces;
+        folderCellSize = displayOption.folderCellSize;
+        folderTopPadding = displayOption.folderTopPadding;
 
         horizontalMargin = displayOption.horizontalMargin;
 
         numShownHotseatIcons = closestProfile.numHotseatIcons;
-        numShrunkenHotseatIcons = closestProfile.numShrunkenHotseatIcons;
         numDatabaseHotseatIcons = deviceType == TYPE_MULTI_DISPLAY
                 ? closestProfile.numDatabaseHotseatIcons : closestProfile.numHotseatIcons;
         hotseatColumnSpan = closestProfile.hotseatColumnSpan;
-        hotseatBorderSpaces = displayOption.hotseatBorderSpaces;
+        hotseatBarBottomSpace = displayOption.hotseatBarBottomSpace;
+        hotseatQsbSpace = displayOption.hotseatQsbSpace;
 
         numAllAppsColumns = closestProfile.numAllAppsColumns;
         numDatabaseAllAppsColumns = deviceType == TYPE_MULTI_DISPLAY
@@ -379,7 +385,7 @@
             devicePaddings = new DevicePaddings(context, devicePaddingId);
         }
 
-        inlineQsb = displayOption.inlineQsb;
+        inlineQsb = closestProfile.inlineQsb;
 
         // If the partner customization apk contains any grid overrides, apply them
         // Supported overrides: numRows, numColumns, iconSize
@@ -387,10 +393,12 @@
 
         final List<DeviceProfile> localSupportedProfiles = new ArrayList<>();
         defaultWallpaperSize = new Point(displayInfo.currentSize);
+        SparseArray<DotRenderer> dotRendererCache = new SparseArray<>();
         for (WindowBounds bounds : displayInfo.supportedBounds) {
             localSupportedProfiles.add(new DeviceProfile.Builder(context, this, displayInfo)
                     .setUseTwoPanels(deviceType == TYPE_MULTI_DISPLAY)
                     .setWindowBounds(bounds)
+                    .setDotRendererCache(dotRendererCache)
                     .build());
 
             // Wallpaper size should be the maximum of the all possible sizes Launcher expects
@@ -633,18 +641,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);
     }
 
@@ -725,6 +721,12 @@
         private static final int DEVICE_CATEGORY_ALL =
                 DEVICE_CATEGORY_PHONE | DEVICE_CATEGORY_TABLET | DEVICE_CATEGORY_MULTI_DISPLAY;
 
+        private static final int INLINE_QSB_FOR_PORTRAIT = 1 << 0;
+        private static final int INLINE_QSB_FOR_LANDSCAPE = 1 << 1;
+        private static final int INLINE_QSB_FOR_TWO_PANEL_PORTRAIT = 1 << 2;
+        private static final int INLINE_QSB_FOR_TWO_PANEL_LANDSCAPE = 1 << 3;
+        private static final int DONT_INLINE_QSB = 0;
+
         public final String name;
         public final int numRows;
         public final int numColumns;
@@ -737,10 +739,13 @@
         private final int numAllAppsColumns;
         private final int numDatabaseAllAppsColumns;
         private final int numHotseatIcons;
-        private final int numShrunkenHotseatIcons;
         private final int numDatabaseHotseatIcons;
+
         private final int[] hotseatColumnSpan = new int[COUNT_SIZES];
 
+        private final boolean[] inlineQsb = new boolean[COUNT_SIZES];
+
+        private int inlineNavButtonsEndSpacing;
         private final String dbFile;
 
         private final int defaultLayoutId;
@@ -775,10 +780,9 @@
 
             numHotseatIcons = a.getInt(
                     R.styleable.GridDisplayOption_numHotseatIcons, numColumns);
-            numShrunkenHotseatIcons = a.getInt(
-                    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(
@@ -790,6 +794,9 @@
                     R.styleable.GridDisplayOption_hotseatColumnSpanTwoPanelPortrait,
                     numColumns);
 
+            inlineNavButtonsEndSpacing =
+                    a.getResourceId(R.styleable.GridDisplayOption_inlineNavButtonsEndSpacing,
+                    R.dimen.taskbar_button_margin_default);
             numFolderRows = a.getInt(
                     R.styleable.GridDisplayOption_numFolderRows, numRows);
             numFolderColumns = a.getInt(
@@ -810,6 +817,19 @@
                         && ((deviceCategory & DEVICE_CATEGORY_MULTI_DISPLAY)
                             == DEVICE_CATEGORY_MULTI_DISPLAY));
 
+            int inlineForRotation = a.getInt(R.styleable.GridDisplayOption_inlineQsb,
+                    DONT_INLINE_QSB);
+            inlineQsb[INDEX_DEFAULT] =
+                    (inlineForRotation & INLINE_QSB_FOR_PORTRAIT) == INLINE_QSB_FOR_PORTRAIT;
+            inlineQsb[INDEX_LANDSCAPE] =
+                    (inlineForRotation & INLINE_QSB_FOR_LANDSCAPE) == INLINE_QSB_FOR_LANDSCAPE;
+            inlineQsb[INDEX_TWO_PANEL_PORTRAIT] =
+                    (inlineForRotation & INLINE_QSB_FOR_TWO_PANEL_PORTRAIT)
+                            == INLINE_QSB_FOR_TWO_PANEL_PORTRAIT;
+            inlineQsb[INDEX_TWO_PANEL_LANDSCAPE] =
+                    (inlineForRotation & INLINE_QSB_FOR_TWO_PANEL_LANDSCAPE)
+                            == INLINE_QSB_FOR_TWO_PANEL_LANDSCAPE;
+
             a.recycle();
             extraAttrs = Themes.createValueMap(context, attrs,
                     IntArray.wrap(R.styleable.GridDisplayOption));
@@ -818,26 +838,22 @@
 
     @VisibleForTesting
     static final class DisplayOption {
-        private static final int INLINE_QSB_FOR_PORTRAIT = 1 << 0;
-        private static final int INLINE_QSB_FOR_LANDSCAPE = 1 << 1;
-        private static final int INLINE_QSB_FOR_TWO_PANEL_PORTRAIT = 1 << 2;
-        private static final int INLINE_QSB_FOR_TWO_PANEL_LANDSCAPE = 1 << 3;
-        private static final int DONT_INLINE_QSB = 0;
-
         public final GridOption grid;
 
         private final float minWidthDps;
         private final float minHeightDps;
         private final boolean canBeDefault;
-        private final boolean[] inlineQsb = new boolean[COUNT_SIZES];
 
         private final PointF[] minCellSize = new PointF[COUNT_SIZES];
 
-        private float folderBorderSpace;
+        private final PointF folderCellSize;
+        private final PointF folderBorderSpaces;
+        private float folderTopPadding;
+
         private final PointF[] borderSpaces = new PointF[COUNT_SIZES];
         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];
@@ -857,19 +873,6 @@
 
             canBeDefault = a.getBoolean(R.styleable.ProfileDisplayOption_canBeDefault, false);
 
-            int inlineForRotation = a.getInt(R.styleable.ProfileDisplayOption_inlineQsb,
-                    DONT_INLINE_QSB);
-            inlineQsb[INDEX_DEFAULT] =
-                    (inlineForRotation & INLINE_QSB_FOR_PORTRAIT) == INLINE_QSB_FOR_PORTRAIT;
-            inlineQsb[INDEX_LANDSCAPE] =
-                    (inlineForRotation & INLINE_QSB_FOR_LANDSCAPE) == INLINE_QSB_FOR_LANDSCAPE;
-            inlineQsb[INDEX_TWO_PANEL_PORTRAIT] =
-                    (inlineForRotation & INLINE_QSB_FOR_TWO_PANEL_PORTRAIT)
-                            == INLINE_QSB_FOR_TWO_PANEL_PORTRAIT;
-            inlineQsb[INDEX_TWO_PANEL_LANDSCAPE] =
-                    (inlineForRotation & INLINE_QSB_FOR_TWO_PANEL_LANDSCAPE)
-                            == INLINE_QSB_FOR_TWO_PANEL_LANDSCAPE;
-
             float x;
             float y;
 
@@ -929,7 +932,20 @@
                     borderSpaceTwoPanelLandscape);
             borderSpaces[INDEX_TWO_PANEL_LANDSCAPE] = new PointF(x, y);
 
-            folderBorderSpace = borderSpace;
+            x = a.getFloat(R.styleable.ProfileDisplayOption_folderCellWidth,
+                    minCellSize[INDEX_DEFAULT].x);
+            y = a.getFloat(R.styleable.ProfileDisplayOption_folderCellHeight,
+                    minCellSize[INDEX_DEFAULT].y);
+            folderCellSize = new PointF(x, y);
+
+            float folderBorderSpace = a.getFloat(R.styleable.ProfileDisplayOption_folderBorderSpace,
+                    borderSpace);
+
+            x = y = folderBorderSpace;
+            folderBorderSpaces = new PointF(x, y);
+
+            folderTopPadding = a.getFloat(R.styleable.ProfileDisplayOption_folderTopPadding,
+                    folderBorderSpaces.y);
 
             x = a.getFloat(R.styleable.ProfileDisplayOption_allAppsCellWidth,
                     minCellSize[INDEX_DEFAULT].x);
@@ -1009,7 +1025,9 @@
 
             allAppsIconSizes[INDEX_DEFAULT] = a.getFloat(
                     R.styleable.ProfileDisplayOption_allAppsIconSize, iconSizes[INDEX_DEFAULT]);
-            allAppsIconSizes[INDEX_LANDSCAPE] = allAppsIconSizes[INDEX_DEFAULT];
+            allAppsIconSizes[INDEX_LANDSCAPE] = a.getFloat(
+                    R.styleable.ProfileDisplayOption_allAppsIconSizeLandscape,
+                    iconSizes[INDEX_DEFAULT]);
             allAppsIconSizes[INDEX_TWO_PANEL_PORTRAIT] = a.getFloat(
                     R.styleable.ProfileDisplayOption_allAppsIconSizeTwoPanelPortrait,
                     allAppsIconSizes[INDEX_DEFAULT]);
@@ -1051,17 +1069,33 @@
                     R.styleable.ProfileDisplayOption_horizontalMarginTwoPanelPortrait,
                     horizontalMargin[INDEX_DEFAULT]);
 
-            hotseatBorderSpaces[INDEX_DEFAULT] = a.getFloat(
-                    R.styleable.ProfileDisplayOption_hotseatBorderSpace, borderSpace);
-            hotseatBorderSpaces[INDEX_LANDSCAPE] = a.getFloat(
-                    R.styleable.ProfileDisplayOption_hotseatBorderSpaceLandscape,
-                    hotseatBorderSpaces[INDEX_DEFAULT]);
-            hotseatBorderSpaces[INDEX_TWO_PANEL_LANDSCAPE] = a.getFloat(
-                    R.styleable.ProfileDisplayOption_hotseatBorderSpaceTwoPanelLandscape,
-                    hotseatBorderSpaces[INDEX_DEFAULT]);
-            hotseatBorderSpaces[INDEX_TWO_PANEL_PORTRAIT] = a.getFloat(
-                    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();
         }
@@ -1084,8 +1118,10 @@
                 allAppsIconSizes[i] = 0;
                 allAppsIconTextSizes[i] = 0;
                 allAppsBorderSpaces[i] = new PointF();
-                inlineQsb[i] = false;
             }
+            folderBorderSpaces = new PointF();
+            folderCellSize = new PointF();
+            folderTopPadding = 0f;
         }
 
         private DisplayOption multiply(float w) {
@@ -1097,7 +1133,8 @@
                 minCellSize[i].x *= w;
                 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;
@@ -1105,8 +1142,11 @@
                 allAppsBorderSpaces[i].x *= w;
                 allAppsBorderSpaces[i].y *= w;
             }
-
-            folderBorderSpace *= w;
+            folderBorderSpaces.x *= w;
+            folderBorderSpaces.y *= w;
+            folderCellSize.x *= w;
+            folderCellSize.y *= w;
+            folderTopPadding *= w;
 
             return this;
         }
@@ -1120,17 +1160,20 @@
                 minCellSize[i].x += p.minCellSize[i].x;
                 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];
                 allAppsIconTextSizes[i] += p.allAppsIconTextSizes[i];
                 allAppsBorderSpaces[i].x += p.allAppsBorderSpaces[i].x;
                 allAppsBorderSpaces[i].y += p.allAppsBorderSpaces[i].y;
-                inlineQsb[i] |= p.inlineQsb[i];
             }
-
-            folderBorderSpace += p.folderBorderSpace;
+            folderBorderSpaces.x += p.folderBorderSpaces.x;
+            folderBorderSpaces.y += p.folderBorderSpaces.y;
+            folderCellSize.x += p.folderCellSize.x;
+            folderCellSize.y += p.folderCellSize.y;
+            folderTopPadding += p.folderTopPadding;
 
             return this;
         }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index ebed31b..a6831aa 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -18,8 +18,6 @@
 
 import static android.app.PendingIntent.FLAG_IMMUTABLE;
 import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
-import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
-import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
 import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
 import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO;
 import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
@@ -30,10 +28,12 @@
 import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
 import static com.android.launcher3.AbstractFloatingView.TYPE_SNACKBAR;
 import static com.android.launcher3.AbstractFloatingView.getTopOpenViewWithType;
+import static com.android.launcher3.LauncherAnimUtils.HOTSEAT_SCALE_PROPERTY_FACTORY;
+import static com.android.launcher3.LauncherAnimUtils.SCALE_INDEX_WIDGET_TRANSITION;
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
+import static com.android.launcher3.LauncherAnimUtils.WORKSPACE_SCALE_PROPERTY_FACTORY;
 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;
@@ -42,6 +42,7 @@
 import static com.android.launcher3.LauncherState.SPRING_LOADED;
 import static com.android.launcher3.Utilities.postAsyncCallback;
 import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.getSupportedActions;
+import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
 import static com.android.launcher3.logging.StatsLogManager.EventEnum;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
@@ -58,7 +59,6 @@
 import static com.android.launcher3.popup.SystemShortcut.WIDGETS;
 import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
 import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
-import static com.android.launcher3.testing.TestProtocol.BAD_STATE;
 import static com.android.launcher3.util.ItemInfoMatcher.forFolderMatch;
 
 import android.animation.Animator;
@@ -97,6 +97,7 @@
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.text.method.TextKeyListener;
+import android.util.FloatProperty;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.KeyEvent;
@@ -174,7 +175,7 @@
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.states.RotationHelper;
 import com.android.launcher3.testing.TestLogging;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.touch.AllAppsSwipeController;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
@@ -194,7 +195,6 @@
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.util.TraceHelper;
-import com.android.launcher3.util.UiThreadHelper;
 import com.android.launcher3.util.ViewOnDrawExecutor;
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.views.FloatingIconView;
@@ -266,7 +266,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 +278,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";
@@ -299,13 +302,15 @@
     public static final int DISPLAY_WORKSPACE_TRACE_COOKIE = 0;
     public static final int DISPLAY_ALL_APPS_TRACE_COOKIE = 1;
 
-    private Configuration mOldConfig;
+    private static final FloatProperty<Workspace<?>> WORKSPACE_WIDGET_SCALE =
+            WORKSPACE_SCALE_PROPERTY_FACTORY.get(SCALE_INDEX_WIDGET_TRANSITION);
+    private static final FloatProperty<Hotseat> HOTSEAT_WIDGET_SCALE =
+            HOTSEAT_SCALE_PROPERTY_FACTORY.get(SCALE_INDEX_WIDGET_TRANSITION);
 
     @Thunk
     Workspace<?> mWorkspace;
     @Thunk
     DragLayer mDragLayer;
-    private DragController mDragController;
 
     private WidgetManagerHelper mAppWidgetManager;
     private LauncherAppWidgetHost mAppWidgetHost;
@@ -369,6 +374,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;
@@ -454,7 +460,6 @@
         super.onCreate(savedInstanceState);
 
         LauncherAppState app = LauncherAppState.getInstance(this);
-        mOldConfig = new Configuration(getResources().getConfiguration());
         mModel = app.getModel();
 
         mRotationHelper = new RotationHelper(this);
@@ -465,7 +470,7 @@
         mIconCache = app.getIconCache();
         mAccessibilityDelegate = createAccessibilityDelegate();
 
-        mDragController = new LauncherDragController(this);
+        initDragController();
         mAllAppsController = new AllAppsTransitionController(this);
         mStateManager = new StateManager<>(this, NORMAL);
 
@@ -475,7 +480,6 @@
         mAppWidgetHost = createAppWidgetHost();
         mAppWidgetHost.startListening();
 
-        inflateRootView(R.layout.launcher);
         setupViews();
         crossFadeWithPreviousAppearance();
         mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots);
@@ -500,7 +504,6 @@
 
         if (!mModel.addCallbacksAndLoad(this)) {
             if (!internalStateHandled) {
-                Log.d(BAD_STATE, "Launcher onCreate not binding sync, prevent drawing");
                 // If we are not binding synchronously, pause drawing until initial bind complete,
                 // so that the system could continue to show the device loading prompt
                 mOnInitialBindListener = Boolean.FALSE::booleanValue;
@@ -603,20 +606,24 @@
         dispatchDeviceProfileChanged();
     }
 
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        int diff = newConfig.diff(mOldConfig);
-        if ((diff & (CONFIG_ORIENTATION | CONFIG_SCREEN_SIZE)) != 0) {
-            onIdpChanged(false);
-        }
-
-        mOldConfig.setTo(newConfig);
-        super.onConfigurationChanged(newConfig);
+    /**
+     * Initializes the drag controller.
+     */
+    protected void initDragController() {
+        mDragController = new LauncherDragController(this);
     }
 
     @Override
     public void onIdpChanged(boolean modelPropertiesChanged) {
-        initDeviceProfile(mDeviceProfile.inv);
+        onHandleConfigurationChanged();
+    }
+
+    @Override
+    protected void onHandleConfigurationChanged() {
+        if (!initDeviceProfile(mDeviceProfile.inv)) {
+            return;
+        }
+
         dispatchDeviceProfileChanged();
         reapplyUi();
         mDragLayer.recreateControllers();
@@ -639,9 +646,17 @@
         mDragLayer.onOneHandedModeStateChanged(activated);
     }
 
-    protected void initDeviceProfile(InvariantDeviceProfile idp) {
+    /**
+     * Returns {@code true} if a new DeviceProfile is initialized, and {@code false} otherwise.
+     */
+    protected boolean initDeviceProfile(InvariantDeviceProfile idp) {
         // Load configuration-specific DeviceProfile
-        mDeviceProfile = idp.getDeviceProfile(this);
+        DeviceProfile deviceProfile = idp.getDeviceProfile(this);
+        if (mDeviceProfile == deviceProfile) {
+            return false;
+        }
+
+        mDeviceProfile = deviceProfile;
         if (isInMultiWindowMode()) {
             mDeviceProfile = mDeviceProfile.getMultiWindowProfile(
                     this, getMultiWindowDisplaySize());
@@ -649,6 +664,7 @@
 
         onDeviceProfileInitiated();
         mModelWriter = mModel.getWriter(getDeviceProfile().isVerticalBarLayout(), true, this);
+        return true;
     }
 
     public RotationHelper getRotationHelper() {
@@ -740,7 +756,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 +1096,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
@@ -1110,6 +1122,7 @@
                         .log(getAllAppsEntryEvent().get());
             }
         }
+        updateDisallowBack();
     }
 
     /**
@@ -1245,6 +1258,7 @@
      * Finds all the views we need and configure them properly.
      */
     protected void setupViews() {
+        inflateRootView(R.layout.launcher);
         mDragLayer = findViewById(R.id.drag_layer);
         mFocusHandler = mDragLayer.getFocusIndicatorHelper();
         mWorkspace = mDragLayer.findViewById(R.id.workspace);
@@ -1640,16 +1654,6 @@
         }
     }
 
-    /**
-     * Hides the keyboard if visible
-     */
-    public void hideKeyboard() {
-        final View v = getWindow().peekDecorView();
-        if (v != null && v.getWindowToken() != null) {
-            UiThreadHelper.hideKeyboardAsync(this, v.getWindowToken());
-        }
-    }
-
     @Override
     public void onRestoreInstanceState(Bundle state) {
         super.onRestoreInstanceState(state);
@@ -2776,7 +2780,7 @@
             View v = getFirstMatch(Collections.singletonList(activeRecyclerView),
                     preferredItem, packageAndUserAndApp);
 
-            if (v != null && activeRecyclerView.getCurrentScrollY() > 0) {
+            if (v != null && activeRecyclerView.computeVerticalScrollOffset() > 0) {
                 RectF locationBounds = new RectF();
                 FloatingIconView.getLocationBoundsForView(this, v, false, locationBounds,
                         new Rect());
@@ -2996,7 +3000,7 @@
         mDragLayer.dump(prefix, writer);
         mStateManager.dump(prefix, writer);
         mPopupDataProvider.dump(prefix, writer);
-        mDeviceProfile.dump(prefix, writer);
+        mDeviceProfile.dump(this, prefix, writer);
 
         try {
             FileLog.flushAll(writer);
@@ -3120,7 +3124,18 @@
 
     public void useFadeOutAnimationForLauncherStart(CancellationSignal signal) { }
 
-    public void onDragLayerHierarchyChanged() { }
+    public void onDragLayerHierarchyChanged() {
+        updateDisallowBack();
+    }
+
+    private void updateDisallowBack() {
+        LauncherRootView rv = getRootView();
+        if (rv != null) {
+            boolean disableBack = getStateManager().getState() == NORMAL
+                    && AbstractFloatingView.getTopOpenView(this) == null;
+            rv.setDisallowBackGesture(disableBack);
+        }
+    }
 
     @Override
     public void returnToHomescreen() {
@@ -3220,7 +3235,10 @@
      * @param progress Transition progress from 0 to 1; where 0 => home and 1 => widgets.
      */
     public void onWidgetsTransition(float progress) {
-        // No-Op
+        float scale = Utilities.mapToRange(progress, 0f, 1f, 1f,
+                mDeviceProfile.bottomSheetWorkspaceScale, EMPHASIZED);
+        WORKSPACE_WIDGET_SCALE.set(getWorkspace(), scale);
+        HOTSEAT_WIDGET_SCALE.set(getHotseat(), scale);
     }
 
     private static class NonConfigInstance {
diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java
index 808bf96..b858d1a 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;
 
@@ -80,9 +81,9 @@
             new MultiScalePropertyFactory<Hotseat>("hotseat_scale_property");
 
     public static final int SCALE_INDEX_UNFOLD_ANIMATION = 1;
-    public static final int SCALE_INDEX_UNLOCK_ANIMATION = 2;
-    public static final int SCALE_INDEX_WORKSPACE_STATE = 3;
-    public static final int SCALE_INDEX_REVEAL_ANIM = 4;
+    public static final int SCALE_INDEX_WORKSPACE_STATE = 2;
+    public static final int SCALE_INDEX_REVEAL_ANIM = 3;
+    public static final int SCALE_INDEX_WIDGET_TRANSITION = 4;
 
     /** Increase the duration if we prevented the fling, as we are going against a high velocity. */
     public static int blockedFlingDurationFactor(float velocity) {
@@ -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/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 597bc8d..ea3f723 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -19,7 +19,6 @@
 import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
 
 import static com.android.launcher3.Utilities.getDevicePrefs;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_THEMED_ICONS;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 import static com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI;
 
@@ -118,12 +117,10 @@
                 observer, MODEL_EXECUTOR.getHandler());
         mOnTerminateCallback.add(iconChangeTracker::close);
         MODEL_EXECUTOR.execute(observer::verifyIconChanged);
-        if (ENABLE_THEMED_ICONS.get()) {
-            SharedPreferences prefs = Utilities.getPrefs(mContext);
-            prefs.registerOnSharedPreferenceChangeListener(observer);
-            mOnTerminateCallback.add(
-                    () -> prefs.unregisterOnSharedPreferenceChangeListener(observer));
-        }
+        SharedPreferences prefs = Utilities.getPrefs(mContext);
+        prefs.registerOnSharedPreferenceChangeListener(observer);
+        mOnTerminateCallback.add(
+                () -> prefs.unregisterOnSharedPreferenceChangeListener(observer));
 
         InstallSessionTracker installSessionTracker =
                 InstallSessionHelper.INSTANCE.get(context).registerInstallTracker(mModel);
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index de0d300..20df897 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -33,6 +33,7 @@
 import android.util.Log;
 import android.util.Pair;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
 
@@ -63,7 +64,7 @@
 import com.android.launcher3.pm.PackageInstallInfo;
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.shortcuts.ShortcutRequest;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.PackageUserKey;
@@ -89,9 +90,11 @@
 
     static final String TAG = "Launcher.Model";
 
+    @NonNull
     private final LauncherAppState mApp;
+    @NonNull
     private final Object mLock = new Object();
-
+    @Nullable
     private LoaderTask mLoaderTask;
     private boolean mIsLoaderTaskRunning;
 
@@ -107,20 +110,25 @@
         }
     }
 
+    @NonNull
     private final ArrayList<Callbacks> mCallbacksList = new ArrayList<>(1);
 
     // < only access in worker thread >
+    @NonNull
     private final AllAppsList mBgAllAppsList;
 
     /**
      * All the static data should be accessed on the background thread, A lock should be acquired
      * on this object when accessing any data from this model.
      */
+    @NonNull
     private final BgDataModel mBgDataModel = new BgDataModel();
 
+    @NonNull
     private final ModelDelegate mModelDelegate;
 
     // Runnable to check if the shortcuts permission has changed.
+    @NonNull
     private final Runnable mDataValidationCheck = new Runnable() {
         @Override
         public void run() {
@@ -130,14 +138,16 @@
         }
     };
 
-    LauncherModel(Context context, LauncherAppState app, IconCache iconCache, AppFilter appFilter,
-            boolean isPrimaryInstance) {
+    LauncherModel(@NonNull final Context context, @NonNull final LauncherAppState app,
+            @NonNull final IconCache iconCache, @NonNull final AppFilter appFilter,
+            final boolean isPrimaryInstance) {
         mApp = app;
         mBgAllAppsList = new AllAppsList(iconCache, appFilter);
         mModelDelegate = ModelDelegate.newInstance(context, app, mBgAllAppsList, mBgDataModel,
                 isPrimaryInstance);
     }
 
+    @NonNull
     public ModelDelegate getModelDelegate() {
         return mModelDelegate;
     }
@@ -145,52 +155,57 @@
     /**
      * Adds the provided items to the workspace.
      */
-    public void addAndBindAddedWorkspaceItems(List<Pair<ItemInfo, Object>> itemList) {
+    public void addAndBindAddedWorkspaceItems(
+            @NonNull final List<Pair<ItemInfo, Object>> itemList) {
         for (Callbacks cb : getCallbacks()) {
             cb.preAddApps();
         }
         enqueueModelUpdateTask(new AddWorkspaceItemsTask(itemList));
     }
 
-    public ModelWriter getWriter(boolean hasVerticalHotseat, boolean verifyChanges,
-            @Nullable Callbacks owner) {
+    @NonNull
+    public ModelWriter getWriter(final boolean hasVerticalHotseat, final boolean verifyChanges,
+            @Nullable final Callbacks owner) {
         return new ModelWriter(mApp.getContext(), this, mBgDataModel,
                 hasVerticalHotseat, verifyChanges, owner);
     }
 
     @Override
-    public void onPackageChanged(String packageName, UserHandle user) {
+    public void onPackageChanged(
+            @NonNull final String packageName, @NonNull final UserHandle user) {
         int op = PackageUpdatedTask.OP_UPDATE;
         enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName));
     }
 
     @Override
-    public void onPackageRemoved(String packageName, UserHandle user) {
+    public void onPackageRemoved(
+            @NonNull final String packageName, @NonNull final UserHandle user) {
         onPackagesRemoved(user, packageName);
     }
 
-    public void onPackagesRemoved(UserHandle user, String... packages) {
+    public void onPackagesRemoved(
+            @NonNull final UserHandle user, @NonNull final String... packages) {
         int op = PackageUpdatedTask.OP_REMOVE;
         FileLog.d(TAG, "package removed received " + TextUtils.join(",", packages));
         enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packages));
     }
 
     @Override
-    public void onPackageAdded(String packageName, UserHandle user) {
+    public void onPackageAdded(@NonNull final String packageName, @NonNull final UserHandle user) {
         int op = PackageUpdatedTask.OP_ADD;
         enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName));
     }
 
     @Override
-    public void onPackagesAvailable(String[] packageNames, UserHandle user,
-            boolean replacing) {
+    public void onPackagesAvailable(@NonNull final String[] packageNames,
+            @NonNull final UserHandle user, final boolean replacing) {
         enqueueModelUpdateTask(
                 new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE, user, packageNames));
     }
 
     @Override
-    public void onPackagesUnavailable(String[] packageNames, UserHandle user,
-            boolean replacing) {
+    public void onPackagesUnavailable(@NonNull final String[] packageNames,
+            @NonNull final UserHandle user, final boolean replacing) {
         if (!replacing) {
             enqueueModelUpdateTask(new PackageUpdatedTask(
                     PackageUpdatedTask.OP_UNAVAILABLE, user, packageNames));
@@ -198,20 +213,22 @@
     }
 
     @Override
-    public void onPackagesSuspended(String[] packageNames, UserHandle user) {
+    public void onPackagesSuspended(
+            @NonNull final String[] packageNames, @NonNull final UserHandle user) {
         enqueueModelUpdateTask(new PackageUpdatedTask(
                 PackageUpdatedTask.OP_SUSPEND, user, packageNames));
     }
 
     @Override
-    public void onPackagesUnsuspended(String[] packageNames, UserHandle user) {
+    public void onPackagesUnsuspended(
+            @NonNull final String[] packageNames, @NonNull final UserHandle user) {
         enqueueModelUpdateTask(new PackageUpdatedTask(
                 PackageUpdatedTask.OP_UNSUSPEND, user, packageNames));
     }
 
     @Override
-    public void onPackageLoadingProgressChanged(
-                String packageName, UserHandle user, float progress) {
+    public void onPackageLoadingProgressChanged(@NonNull final String packageName,
+            @NonNull final UserHandle user, final float progress) {
         if (Utilities.ATLEAST_S) {
             enqueueModelUpdateTask(new PackageIncrementalDownloadUpdatedTask(
                     packageName, user, progress));
@@ -219,8 +236,8 @@
     }
 
     @Override
-    public void onShortcutsChanged(String packageName, List<ShortcutInfo> shortcuts,
-            UserHandle user) {
+    public void onShortcutsChanged(@NonNull final String packageName,
+            @NonNull final List<ShortcutInfo> shortcuts, @NonNull final UserHandle user) {
         enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, true));
     }
 
@@ -228,7 +245,8 @@
      * Called when the icon for an app changes, outside of package event
      */
     @WorkerThread
-    public void onAppIconChanged(String packageName, UserHandle user) {
+    public void onAppIconChanged(@NonNull final String packageName,
+            @NonNull final UserHandle user) {
         // Update the icon for the calendar package
         Context context = mApp.getContext();
         onPackageChanged(packageName, user);
@@ -256,7 +274,7 @@
         MODEL_EXECUTOR.execute(mModelDelegate::destroy);
     }
 
-    public void onBroadcastIntent(Intent intent) {
+    public void onBroadcastIntent(@NonNull final Intent intent) {
         if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent);
         final String action = intent.getAction();
         if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
@@ -322,7 +340,7 @@
     /**
      * Removes an existing callback
      */
-    public void removeCallbacks(Callbacks callbacks) {
+    public void removeCallbacks(@NonNull final Callbacks callbacks) {
         synchronized (mCallbacksList) {
             Preconditions.assertUIThread();
             if (mCallbacksList.remove(callbacks)) {
@@ -338,7 +356,7 @@
      * Adds a callbacks to receive model updates
      * @return true if workspace load was performed synchronously
      */
-    public boolean addCallbacksAndLoad(Callbacks callbacks) {
+    public boolean addCallbacksAndLoad(@NonNull final Callbacks callbacks) {
         synchronized (mLock) {
             addCallbacks(callbacks);
             return startLoader(new Callbacks[] { callbacks });
@@ -349,7 +367,7 @@
     /**
      * Adds a callbacks to receive model updates
      */
-    public void addCallbacks(Callbacks callbacks) {
+    public void addCallbacks(@NonNull final Callbacks callbacks) {
         Preconditions.assertUIThread();
         synchronized (mCallbacksList) {
             if (TestProtocol.sDebugTracing) {
@@ -370,7 +388,7 @@
         return startLoader(new Callbacks[0]);
     }
 
-    private boolean startLoader(Callbacks[] newCallbacks) {
+    private boolean startLoader(@NonNull final Callbacks[] newCallbacks) {
         // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
         ItemInstallQueue.INSTANCE.get(mApp.getContext())
                 .pauseModelPush(ItemInstallQueue.FLAG_LOADER_RUNNING);
@@ -433,7 +451,7 @@
      * Loads the model if not loaded
      * @param callback called with the data model upon successful load or null on model thread.
      */
-    public void loadAsync(Consumer<BgDataModel> callback) {
+    public void loadAsync(@NonNull final Consumer<BgDataModel> callback) {
         synchronized (mLock) {
             if (!mModelLoaded && !mIsLoaderTaskRunning) {
                 startLoader();
@@ -443,11 +461,12 @@
     }
 
     @Override
-    public void onInstallSessionCreated(final PackageInstallInfo sessionInfo) {
+    public void onInstallSessionCreated(@NonNull final PackageInstallInfo sessionInfo) {
         if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) {
             enqueueModelUpdateTask(new BaseModelUpdateTask() {
                 @Override
-                public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+                public void execute(@NonNull final LauncherAppState app,
+                        @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) {
                     apps.addPromiseApp(app.getContext(), sessionInfo);
                     bindApplicationsIfNeeded();
                 }
@@ -456,13 +475,12 @@
     }
 
     @Override
-    public void onSessionFailure(String packageName, UserHandle user) {
-        if (!FeatureFlags.PROMISE_APPS_NEW_INSTALLS.get()) {
-            return;
-        }
+    public void onSessionFailure(@NonNull final String packageName,
+            @NonNull final UserHandle user) {
         enqueueModelUpdateTask(new BaseModelUpdateTask() {
             @Override
-            public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+            public void execute(@NonNull final LauncherAppState app,
+                    @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) {
                 final IntSet removedIds = new IntSet();
                 synchronized (dataModel) {
                     for (ItemInfo info : dataModel.itemsIdMap) {
@@ -486,7 +504,7 @@
     }
 
     @Override
-    public void onPackageStateChanged(PackageInstallInfo installInfo) {
+    public void onPackageStateChanged(@NonNull final PackageInstallInfo installInfo) {
         enqueueModelUpdateTask(new PackageInstallStateChangedTask(installInfo));
     }
 
@@ -494,7 +512,8 @@
      * Updates the icons and label of all pending icons for the provided package name.
      */
     @Override
-    public void onUpdateSessionDisplay(PackageUserKey key, PackageInstaller.SessionInfo info) {
+    public void onUpdateSessionDisplay(@NonNull final PackageUserKey key,
+            @NonNull final PackageInstaller.SessionInfo info) {
         mApp.getIconCache().updateSessionCache(key, info);
 
         HashSet<String> packages = new HashSet<>();
@@ -505,9 +524,10 @@
 
     public class LoaderTransaction implements AutoCloseable {
 
+        @NonNull
         private final LoaderTask mTask;
 
-        private LoaderTransaction(LoaderTask task) throws CancellationException {
+        private LoaderTransaction(@NonNull final LoaderTask task) throws CancellationException {
             synchronized (mLock) {
                 if (mLoaderTask != task) {
                     throw new CancellationException("Loader already stopped");
@@ -537,7 +557,8 @@
         }
     }
 
-    public LoaderTransaction beginLoader(LoaderTask task) throws CancellationException {
+    public LoaderTransaction beginLoader(@NonNull final LoaderTask task)
+            throws CancellationException {
         return new LoaderTransaction(task);
     }
 
@@ -554,7 +575,8 @@
     /**
      * Called when the icons for packages have been updated in the icon cache.
      */
-    public void onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandle user) {
+    public void onPackageIconsUpdated(@NonNull final HashSet<String> updatedPackages,
+            @NonNull final UserHandle user) {
         // If any package icon has changed (app was updated while launcher was dead),
         // update the corresponding shortcuts.
         enqueueModelUpdateTask(new CacheDataUpdatedTask(
@@ -564,17 +586,19 @@
     /**
      * Called when the labels for the widgets has updated in the icon cache.
      */
-    public void onWidgetLabelsUpdated(HashSet<String> updatedPackages, UserHandle user) {
+    public void onWidgetLabelsUpdated(@NonNull final HashSet<String> updatedPackages,
+            @NonNull final UserHandle user) {
         enqueueModelUpdateTask(new BaseModelUpdateTask() {
             @Override
-            public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+            public void execute(@NonNull final LauncherAppState app,
+                    @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) {
                 dataModel.widgetsModel.onPackageIconsUpdated(updatedPackages, user, app);
                 bindUpdatedWidgets(dataModel);
             }
         });
     }
 
-    public void enqueueModelUpdateTask(ModelUpdateTask task) {
+    public void enqueueModelUpdateTask(@NonNull final ModelUpdateTask task) {
         if (mModelDestroyed) {
             return;
         }
@@ -588,7 +612,7 @@
      */
     public interface CallbackTask {
 
-        void execute(Callbacks callbacks);
+        void execute(@NonNull Callbacks callbacks);
     }
 
     /**
@@ -599,12 +623,14 @@
         /**
          * Called before the task is posted to initialize the internal state.
          */
-        void init(LauncherAppState app, LauncherModel model,
-                BgDataModel dataModel, AllAppsList allAppsList, Executor uiExecutor);
+        void init(@NonNull LauncherAppState app, @NonNull LauncherModel model,
+                @NonNull BgDataModel dataModel, @NonNull AllAppsList allAppsList,
+                @NonNull Executor uiExecutor);
 
     }
 
-    public void updateAndBindWorkspaceItem(WorkspaceItemInfo si, ShortcutInfo info) {
+    public void updateAndBindWorkspaceItem(@NonNull final WorkspaceItemInfo si,
+            @NonNull final ShortcutInfo info) {
         updateAndBindWorkspaceItem(() -> {
             si.updateFromDeepShortcutInfo(info, mApp.getContext());
             mApp.getIconCache().getShortcutIcon(si, info);
@@ -615,10 +641,12 @@
     /**
      * Utility method to update a shortcut on the background thread.
      */
-    public void updateAndBindWorkspaceItem(final Supplier<WorkspaceItemInfo> itemProvider) {
+    public void updateAndBindWorkspaceItem(
+            @NonNull final Supplier<WorkspaceItemInfo> itemProvider) {
         enqueueModelUpdateTask(new BaseModelUpdateTask() {
             @Override
-            public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+            public void execute(@NonNull final LauncherAppState app,
+                    @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) {
                 WorkspaceItemInfo info = itemProvider.get();
                 getModelWriter().updateItemInDatabase(info);
                 ArrayList<WorkspaceItemInfo> update = new ArrayList<>();
@@ -631,14 +659,16 @@
     public void refreshAndBindWidgetsAndShortcuts(@Nullable final PackageUserKey packageUser) {
         enqueueModelUpdateTask(new BaseModelUpdateTask() {
             @Override
-            public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+            public void execute(@NonNull final LauncherAppState app,
+                    @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) {
                 dataModel.widgetsModel.update(app, packageUser);
                 bindUpdatedWidgets(dataModel);
             }
         });
     }
 
-    public void dumpState(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+    public void dumpState(@Nullable final String prefix, @Nullable final FileDescriptor fd,
+            @NonNull final PrintWriter writer, @NonNull final String[] args) {
         if (args.length > 0 && TextUtils.equals(args[0], "--all")) {
             writer.println(prefix + "All apps list: size=" + mBgAllAppsList.data.size());
             for (AppInfo info : mBgAllAppsList.data) {
@@ -664,6 +694,7 @@
     /**
      * Returns an array of currently attached callbacks
      */
+    @NonNull
     public Callbacks[] getCallbacks() {
         synchronized (mCallbacksList) {
             return mCallbacksList.toArray(new Callbacks[mCallbacksList.size()]);
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/LauncherRootView.java b/src/com/android/launcher3/LauncherRootView.java
index a5c5c02..1592154 100644
--- a/src/com/android/launcher3/LauncherRootView.java
+++ b/src/com/android/launcher3/LauncherRootView.java
@@ -55,6 +55,8 @@
 
     @Override
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+        mActivity.handleConfigurationChanged(mActivity.getResources().getConfiguration());
+
         insets = WindowManagerProxy.INSTANCE.get(getContext())
                 .normalizeWindowInsets(getContext(), insets, mTempRect);
         handleSystemWindowInsets(mTempRect);
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index ea6a919..31a7d18 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -19,16 +19,16 @@
 import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
-import static com.android.launcher3.testing.TestProtocol.ALL_APPS_STATE_ORDINAL;
-import static com.android.launcher3.testing.TestProtocol.BACKGROUND_APP_STATE_ORDINAL;
-import static com.android.launcher3.testing.TestProtocol.HINT_STATE_ORDINAL;
-import static com.android.launcher3.testing.TestProtocol.HINT_STATE_TWO_BUTTON_ORDINAL;
-import static com.android.launcher3.testing.TestProtocol.NORMAL_STATE_ORDINAL;
-import static com.android.launcher3.testing.TestProtocol.OVERVIEW_MODAL_TASK_STATE_ORDINAL;
-import static com.android.launcher3.testing.TestProtocol.OVERVIEW_SPLIT_SELECT_ORDINAL;
-import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
-import static com.android.launcher3.testing.TestProtocol.QUICK_SWITCH_STATE_ORDINAL;
-import static com.android.launcher3.testing.TestProtocol.SPRING_LOADED_STATE_ORDINAL;
+import static com.android.launcher3.testing.shared.TestProtocol.ALL_APPS_STATE_ORDINAL;
+import static com.android.launcher3.testing.shared.TestProtocol.BACKGROUND_APP_STATE_ORDINAL;
+import static com.android.launcher3.testing.shared.TestProtocol.HINT_STATE_ORDINAL;
+import static com.android.launcher3.testing.shared.TestProtocol.HINT_STATE_TWO_BUTTON_ORDINAL;
+import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
+import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_MODAL_TASK_STATE_ORDINAL;
+import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_SPLIT_SELECT_ORDINAL;
+import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_STATE_ORDINAL;
+import static com.android.launcher3.testing.shared.TestProtocol.QUICK_SWITCH_STATE_ORDINAL;
+import static com.android.launcher3.testing.shared.TestProtocol.SPRING_LOADED_STATE_ORDINAL;
 
 import android.content.Context;
 import android.graphics.Color;
@@ -38,7 +38,7 @@
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.states.HintState;
 import com.android.launcher3.states.SpringLoadedState;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.uioverrides.states.AllAppsState;
 import com.android.launcher3.uioverrides.states.OverviewState;
 
@@ -71,17 +71,14 @@
     public static final int FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED = BaseState.getFlag(2);
     // Flag to indicate that workspace should draw page background
     public static final int FLAG_WORKSPACE_HAS_BACKGROUNDS = BaseState.getFlag(3);
-    // True if the back button should be hidden when in this state (assuming no floating views are
-    // open, launcher has window focus, etc).
-    public static final int FLAG_HIDE_BACK_BUTTON = BaseState.getFlag(4);
     // Flag to indicate if the state would have scrim over sysui region: statu sbar and nav bar
-    public static final int FLAG_HAS_SYS_UI_SCRIM = BaseState.getFlag(5);
+    public static final int FLAG_HAS_SYS_UI_SCRIM = BaseState.getFlag(4);
     // Flag to inticate that all popups should be closed when this state is enabled.
-    public static final int FLAG_CLOSE_POPUPS = BaseState.getFlag(6);
-    public static final int FLAG_OVERVIEW_UI = BaseState.getFlag(7);
+    public static final int FLAG_CLOSE_POPUPS = BaseState.getFlag(5);
+    public static final int FLAG_OVERVIEW_UI = BaseState.getFlag(6);
 
     // Flag indicating that hotseat and its contents are not accessible.
-    public static final int FLAG_HOTSEAT_INACCESSIBLE = BaseState.getFlag(8);
+    public static final int FLAG_HOTSEAT_INACCESSIBLE = BaseState.getFlag(7);
 
 
     public static final float NO_OFFSET = 0;
@@ -110,8 +107,7 @@
      */
     public static final LauncherState NORMAL = new LauncherState(NORMAL_STATE_ORDINAL,
             LAUNCHER_STATE_HOME,
-            FLAG_DISABLE_RESTORE | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED | FLAG_HIDE_BACK_BUTTON |
-                    FLAG_HAS_SYS_UI_SCRIM) {
+            FLAG_DISABLE_RESTORE | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED | FLAG_HAS_SYS_UI_SCRIM) {
         @Override
         public int getTransitionDuration(Context context, boolean isToState) {
             // Arbitrary duration, when going to NORMAL we use the state we're coming from instead.
@@ -208,14 +204,18 @@
         return (getVisibleElements(launcher) & elements) == elements;
     }
 
-    /** Returns whether taskbar is stashed and thus should replace hotseat with a handle */
+    /**
+     * Returns whether taskbar is stashed and thus should either:
+     * 1) replace hotseat or taskbar icons with a handle in gesture navigation mode or
+     * 2) fade out the hotseat or taskbar icons in 3-button navigation mode.
+     */
     public boolean isTaskbarStashed(Launcher launcher) {
         return false;
     }
 
     /** Returns whether taskbar is aligned with the hotseat vs position inside apps */
     public boolean isTaskbarAlignedWithHotseat(Launcher launcher) {
-        return !isTaskbarStashed(launcher);
+        return true;
     }
 
     /**
@@ -261,7 +261,8 @@
      *
      * 0 means completely zoomed in, without blurs. 1 is zoomed out, with blurs.
      */
-    public final float getDepth(Context context) {
+    public final  <DEVICE_PROFILE_CONTEXT extends Context & DeviceProfile.DeviceProfileListenable>
+            float getDepth(DEVICE_PROFILE_CONTEXT context) {
         return getDepth(context,
                 BaseDraggingActivity.fromContext(context).getDeviceProfile().isMultiWindowMode);
     }
@@ -271,14 +272,16 @@
      *
      * @see #getDepth(Context).
      */
-    public final float getDepth(Context context, boolean isMultiWindowMode) {
+    public final <DEVICE_PROFILE_CONTEXT extends Context & DeviceProfile.DeviceProfileListenable>
+            float getDepth(DEVICE_PROFILE_CONTEXT context, boolean isMultiWindowMode) {
         if (isMultiWindowMode) {
             return 0;
         }
         return getDepthUnchecked(context);
     }
 
-    protected float getDepthUnchecked(Context context) {
+    protected <DEVICE_PROFILE_CONTEXT extends Context & DeviceProfile.DeviceProfileListenable>
+            float getDepthUnchecked(DEVICE_PROFILE_CONTEXT context) {
         return 0f;
     }
 
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index cba0b7d..68c54c7 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -259,8 +259,13 @@
         abortScrollerAnimation(true);
     }
 
+    protected void onScrollerAnimationAborted() {
+        // No-Op
+    }
+
     private void abortScrollerAnimation(boolean resetNextPage) {
         mScroller.abortAnimation();
+        onScrollerAnimationAborted();
         // We need to clean up the next page here to avoid computeScrollHelper from
         // updating current page on the pass.
         if (resetNextPage) {
@@ -555,11 +560,11 @@
             if (mAllowOverScroll) {
                 if (newPos < mMinScroll && oldPos >= mMinScroll) {
                     mEdgeGlowLeft.onAbsorb((int) mScroller.getCurrVelocity());
-                    mScroller.abortAnimation();
+                    abortScrollerAnimation(false);
                     onEdgeAbsorbingScroll();
                 } else if (newPos > mMaxScroll && oldPos <= mMaxScroll) {
                     mEdgeGlowRight.onAbsorb((int) mScroller.getCurrVelocity());
-                    mScroller.abortAnimation();
+                    abortScrollerAnimation(false);
                     onEdgeAbsorbingScroll();
                 }
             }
@@ -569,7 +574,7 @@
             int finalPos = mOrientationHandler.getPrimaryValue(mScroller.getFinalX(),
                     mScroller.getFinalY());
             if (newPos == finalPos && mEdgeGlowLeft.isFinished() && mEdgeGlowRight.isFinished()) {
-                mScroller.abortAnimation();
+                abortScrollerAnimation(false);
             }
 
             invalidate();
@@ -1187,9 +1192,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/PendingAddItemInfo.java b/src/com/android/launcher3/PendingAddItemInfo.java
index be994ee..b7a22fc 100644
--- a/src/com/android/launcher3/PendingAddItemInfo.java
+++ b/src/com/android/launcher3/PendingAddItemInfo.java
@@ -18,6 +18,7 @@
 
 import android.content.ComponentName;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.model.data.ItemInfo;
@@ -43,6 +44,7 @@
     /**
      * Returns shallow copy of the object.
      */
+    @NonNull
     @Override
     public ItemInfo makeShallowCopy() {
         PendingAddItemInfo itemInfo = new PendingAddItemInfo();
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
index f8bc1f4..0ee7aae 100644
--- a/src/com/android/launcher3/SecondaryDropTarget.java
+++ b/src/com/android/launcher3/SecondaryDropTarget.java
@@ -159,7 +159,7 @@
                 return RECONFIGURE;
             }
             return INVALID;
-        } else if (FeatureFlags.ENABLE_PREDICTION_DISMISS.get() && info.isPredictedItem()) {
+        } else if (info.isPredictedItem()) {
             return DISMISS_PREDICTION;
         }
 
diff --git a/src/com/android/launcher3/SessionCommitReceiver.java b/src/com/android/launcher3/SessionCommitReceiver.java
index b81637f..bcd4c3f 100644
--- a/src/com/android/launcher3/SessionCommitReceiver.java
+++ b/src/com/android/launcher3/SessionCommitReceiver.java
@@ -31,7 +31,7 @@
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.model.ItemInstallQueue;
 import com.android.launcher3.pm.InstallSessionHelper;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.util.Executors;
 
 /**
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index 5583eae..486a68f 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -25,6 +25,7 @@
 import android.app.WallpaperManager;
 import android.content.Context;
 import android.graphics.Point;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.view.MotionEvent;
 import android.view.View;
@@ -32,6 +33,7 @@
 
 import com.android.launcher3.CellLayout.ContainerType;
 import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.widget.NavigableAppWidgetHostView;
 
@@ -109,8 +111,9 @@
         if (child instanceof NavigableAppWidgetHostView) {
             DeviceProfile profile = mActivity.getDeviceProfile();
             ((NavigableAppWidgetHostView) child).getWidgetInset(profile, mTempRect);
+            final PointF appWidgetScale = profile.getAppWidgetScale((ItemInfo) child.getTag());
             lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
-                    profile.appWidgetScale.x, profile.appWidgetScale.y, mBorderSpace, mTempRect);
+                    appWidgetScale.x, appWidgetScale.y, mBorderSpace, mTempRect);
         } else {
             lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
                     mBorderSpace, null);
@@ -133,8 +136,9 @@
 
         if (child instanceof NavigableAppWidgetHostView) {
             ((NavigableAppWidgetHostView) child).getWidgetInset(dp, mTempRect);
+            final PointF appWidgetScale = dp.getAppWidgetScale((ItemInfo) child.getTag());
             lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
-                    dp.appWidgetScale.x, dp.appWidgetScale.y, mBorderSpace, mTempRect);
+                    appWidgetScale.x, appWidgetScale.y, mBorderSpace, mTempRect);
         } else {
             lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
                     mBorderSpace, null);
@@ -187,8 +191,9 @@
 
             // Scale and center the widget to fit within its cells.
             DeviceProfile profile = mActivity.getDeviceProfile();
-            float scaleX = profile.appWidgetScale.x;
-            float scaleY = profile.appWidgetScale.y;
+            final PointF appWidgetScale = profile.getAppWidgetScale((ItemInfo) child.getTag());
+            float scaleX = appWidgetScale.x;
+            float scaleY = appWidgetScale.y;
 
             nahv.setScaleToFit(Math.min(scaleX, scaleY));
             nahv.setTranslationForCentering(-(lp.width - (lp.width * scaleX)) / 2.0f,
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 7b96838..616b08a 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -75,6 +75,7 @@
 
 import androidx.annotation.ChecksSdkIntAtLeast;
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.core.graphics.ColorUtils;
 
 import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
@@ -88,6 +89,7 @@
 import com.android.launcher3.pm.ShortcutConfigActivityInfo;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.shortcuts.ShortcutRequest;
+import com.android.launcher3.testing.shared.ResourceUtils;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
@@ -525,10 +527,11 @@
     }
 
     public static int pxFromSp(float size, DisplayMetrics metrics, float scale) {
-        return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
-                size, metrics) * scale);
+        float value = scale * TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, size, metrics);
+        return ResourceUtils.roundPxValueFromFloat(value);
     }
 
+
     public static String createDbSelectionQuery(String columnName, IntArray values) {
         return String.format(Locale.ENGLISH, "%s IN (%s)", columnName, values.toConcatString());
     }
@@ -587,15 +590,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
@@ -932,4 +926,12 @@
         }
         return options;
     }
+
+    public static boolean bothNull(@Nullable Object a, @Nullable Object b) {
+        return a == null && b == null;
+    }
+
+    public static boolean bothNonNull(@Nullable Object a, @Nullable Object b) {
+        return a != null && b != null;
+    }
 }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index b96c40d..b49d646 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -30,7 +30,6 @@
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPELEFT;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPERIGHT;
-import static com.android.launcher3.testing.TestProtocol.BAD_STATE;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -45,6 +44,7 @@
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Point;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
@@ -152,14 +152,10 @@
 
     public static final int DEFAULT_PAGE = 0;
 
-    private static final int DEFAULT_SMARTSPACE_HEIGHT = 1;
-
-    private static final int EXPANDED_SMARTSPACE_HEIGHT = 2;
-
     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();
@@ -169,7 +165,7 @@
     /**
      * CellInfo for the cell that is currently being dragged
      */
-    private CellLayout.CellInfo mDragInfo;
+    protected CellLayout.CellInfo mDragInfo;
 
     /**
      * Target drop area calculated during last acceptDrop call.
@@ -195,7 +191,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];
@@ -214,7 +210,7 @@
     private boolean mUnlockWallpaperFromDefaultPageOnLayout;
 
     public static final int REORDER_TIMEOUT = 650;
-    private final Alarm mReorderAlarm = new Alarm();
+    protected final Alarm mReorderAlarm = new Alarm();
     private PreviewBackground mFolderCreateBg;
     private FolderIcon mDragOverFolderIcon = null;
     private boolean mCreateUserFolderOnDrop = false;
@@ -242,7 +238,7 @@
     private static final int DRAG_MODE_CREATE_FOLDER = 1;
     private static final int DRAG_MODE_ADD_TO_FOLDER = 2;
     private static final int DRAG_MODE_REORDER = 3;
-    private int mDragMode = DRAG_MODE_NONE;
+    protected int mDragMode = DRAG_MODE_NONE;
     @Thunk int mLastReorderX = -1;
     @Thunk int mLastReorderY = -1;
 
@@ -370,7 +366,8 @@
             float scale = 1;
             if (isWidget) {
                 DeviceProfile profile = mLauncher.getDeviceProfile();
-                scale = Utilities.shrinkRect(r, profile.appWidgetScale.x, profile.appWidgetScale.y);
+                final PointF appWidgetScale = profile.getAppWidgetScale(null);
+                scale = Utilities.shrinkRect(r, appWidgetScale.x, appWidgetScale.y);
             }
             size[0] = r.width();
             size[1] = r.height();
@@ -561,10 +558,8 @@
                     .inflate(R.layout.search_container_workspace, firstPage, false);
         }
 
-        int cellVSpan = FeatureFlags.EXPANDED_SMARTSPACE.get()
-                ? EXPANDED_SMARTSPACE_HEIGHT : DEFAULT_SMARTSPACE_HEIGHT;
         int cellHSpan = mLauncher.getDeviceProfile().inv.numSearchContainerColumns;
-        CellLayout.LayoutParams lp = new CellLayout.LayoutParams(0, 0, cellHSpan, cellVSpan);
+        CellLayout.LayoutParams lp = new CellLayout.LayoutParams(0, 0, cellHSpan, 1);
         lp.canReorder = false;
         if (!firstPage.addViewToCellLayout(mQsb, 0, R.id.search_container_workspace, lp, true)) {
             Log.e(TAG, "Failed to add to item at (0, 0) to CellLayout");
@@ -1248,7 +1243,6 @@
         // different effects based on device performance. On at least one relatively high-end
         // device I've tried, translating the launcher causes things to get quite laggy.
         mLauncher.getDragLayer().setTranslationX(transX);
-        Log.d(BAD_STATE, "Workspace onOverlayScrollChanged DragLayer ALPHA_INDEX_OVERLAY=" + alpha);
         mLauncher.getDragLayer().getAlphaProperty(ALPHA_INDEX_OVERLAY).setValue(alpha);
     }
 
@@ -1980,10 +1974,10 @@
 
                 // If the item being dropped is a shortcut and the nearest drop
                 // cell also contains a shortcut, then create a folder with the two shortcuts.
-                if (createUserFolderIfNecessary(cell, container,
-                        dropTargetLayout, mTargetCell, distance, false, d)
+                if (createUserFolderIfNecessary(cell, container, dropTargetLayout, mTargetCell,
+                        distance, false, d)
                         || addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
-                                distance, d, false)) {
+                        distance, d, false)) {
                     mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
                     return;
                 }
@@ -2012,8 +2006,8 @@
                     mTargetCell[0] = mTargetCell[1] = -1;
                 } else {
                     mTargetCell = dropTargetLayout.performReorder((int) mDragViewVisualCenter[0],
-                            (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell,
-                            mTargetCell, resultSpan, CellLayout.MODE_ON_DROP);
+                            (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
+                            cell, mTargetCell, resultSpan, CellLayout.MODE_ON_DROP);
                 }
 
                 boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
@@ -2123,7 +2117,8 @@
                 final ItemInfo info = (ItemInfo) cell.getTag();
                 boolean isWidget = info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
                         || info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
-                if (isWidget) {
+                if (isWidget && dropTargetLayout != null) {
+                    // animate widget to a valid place
                     int animationType = resizeOnDrop ? ANIMATE_INTO_POSITION_AND_RESIZE :
                             ANIMATE_INTO_POSITION_AND_DISAPPEAR;
                     animateWidgetDrop(info, parent, d.dragView, null, animationType, cell, false);
@@ -2311,7 +2306,7 @@
         }
     }
 
-    private void cleanupFolderCreation() {
+    protected void cleanupFolderCreation() {
         if (mFolderCreateBg != null) {
             mFolderCreateBg.animateToRest();
         }
@@ -2324,7 +2319,7 @@
         }
     }
 
-    private void cleanupReorder(boolean cancelAlarm) {
+    protected void cleanupReorder(boolean cancelAlarm) {
         // Any pending reorders are canceled
         if (cancelAlarm) {
             mReorderAlarm.cancelAlarm();
@@ -2416,26 +2411,7 @@
                     mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX,
                     item.spanY, child, mTargetCell);
 
-            if (!nearestDropOccupied) {
-                mDragTargetLayout.visualizeDropLocation(mTargetCell[0], mTargetCell[1],
-                        item.spanX, item.spanY, d);
-            } else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER)
-                    && !mReorderAlarm.alarmPending()
-                    && (mLastReorderX != reorderX || mLastReorderY != reorderY)
-                    && targetCellDistance < mDragTargetLayout.getReorderRadius(mTargetCell)) {
-
-                int[] resultSpan = new int[2];
-                mDragTargetLayout.performReorder((int) mDragViewVisualCenter[0],
-                        (int) mDragViewVisualCenter[1], minSpanX, minSpanY, item.spanX, item.spanY,
-                        child, mTargetCell, resultSpan, CellLayout.MODE_SHOW_REORDER_HINT);
-
-                // Otherwise, if we aren't adding to or creating a folder and there's no pending
-                // reorder, then we schedule a reorder
-                ReorderAlarmListener listener = new ReorderAlarmListener(mDragViewVisualCenter,
-                        minSpanX, minSpanY, item.spanX, item.spanY, d, child);
-                mReorderAlarm.setOnAlarmListener(listener);
-                mReorderAlarm.setAlarm(REORDER_TIMEOUT);
-            }
+            manageReorderOnDragOver(d, targetCellDistance, nearestDropOccupied, minSpanX, minSpanY);
 
             if (mDragMode == DRAG_MODE_CREATE_FOLDER || mDragMode == DRAG_MODE_ADD_TO_FOLDER ||
                     !nearestDropOccupied) {
@@ -2446,6 +2422,35 @@
         }
     }
 
+    protected void manageReorderOnDragOver(DragObject d, float targetCellDistance,
+            boolean nearestDropOccupied, int minSpanX, int minSpanY) {
+
+        ItemInfo item = d.dragInfo;
+        final View child = (mDragInfo == null) ? null : mDragInfo.cell;
+        int reorderX = mTargetCell[0];
+        int reorderY = mTargetCell[1];
+        if (!nearestDropOccupied) {
+            mDragTargetLayout.visualizeDropLocation(mTargetCell[0], mTargetCell[1],
+                    item.spanX, item.spanY, d);
+        } else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER)
+                && !mReorderAlarm.alarmPending()
+                && (mLastReorderX != reorderX || mLastReorderY != reorderY)
+                && targetCellDistance < mDragTargetLayout.getReorderRadius(mTargetCell, item.spanX,
+                item.spanY)) {
+
+            int[] resultSpan = new int[2];
+            mDragTargetLayout.performReorder((int) mDragViewVisualCenter[0],
+                    (int) mDragViewVisualCenter[1], minSpanX, minSpanY, item.spanX, item.spanY,
+                    child, mTargetCell, resultSpan, CellLayout.MODE_SHOW_REORDER_HINT);
+
+            // Otherwise, if we aren't adding to or creating a folder and there's no pending
+            // reorder, then we schedule a reorder
+            ReorderAlarmListener listener = new ReorderAlarmListener(mDragViewVisualCenter,
+                    minSpanX, minSpanY, item.spanX, item.spanY, d, child);
+            mReorderAlarm.setOnAlarmListener(listener);
+            mReorderAlarm.setAlarm(REORDER_TIMEOUT);
+        }
+    }
     /**
      * Updates {@link #mDragTargetLayout} and {@link #mDragOverlappingLayout}
      * based on the DragObject's position.
@@ -2889,7 +2894,8 @@
                 r.top -= widgetPadding.top;
                 r.bottom += widgetPadding.bottom;
             }
-            Utilities.shrinkRect(r, profile.appWidgetScale.x, profile.appWidgetScale.y);
+            PointF appWidgetScale = profile.getAppWidgetScale(null);
+            Utilities.shrinkRect(r, appWidgetScale.x, appWidgetScale.y);
         }
 
         mTempFXY[0] = r.left;
@@ -3432,7 +3438,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/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index 53a6fd7..08b42cd 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -15,8 +15,9 @@
  */
 package com.android.launcher3.allapps;
 
+import static com.android.launcher3.allapps.BaseAllAppsContainerView.AdapterHolder.SEARCH;
+
 import android.content.Context;
-import android.content.Intent;
 import android.util.AttributeSet;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -32,11 +33,9 @@
 import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
 import com.android.launcher3.allapps.search.SearchAdapterProvider;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.views.AppLauncher;
 
 import java.util.ArrayList;
-import java.util.Objects;
 
 /**
  * All apps container view with search support for use in a dragging activity.
@@ -46,6 +45,11 @@
 public class ActivityAllAppsContainerView<T extends Context & AppLauncher
         & DeviceProfileListenable> extends BaseAllAppsContainerView<T> {
 
+    private static final long DEFAULT_SEARCH_TRANSITION_DURATION_MS = 300;
+
+    // Used to animate Search results out and A-Z apps in, or vice-versa.
+    private final SearchTransitionController mSearchTransitionController;
+
     protected SearchUiManager mSearchUiManager;
     /**
      * View that defines the search box. Result is rendered inside the recycler view defined in the
@@ -54,6 +58,7 @@
     private View mSearchContainer;
     /** {@code true} when rendered view is in search state instead of the scroll state. */
     private boolean mIsSearching;
+    private boolean mRebindAdaptersAfterSearchAnimation;
 
     public ActivityAllAppsContainerView(Context context) {
         this(context, null);
@@ -65,6 +70,8 @@
 
     public ActivityAllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
+
+        mSearchTransitionController = new SearchTransitionController(this);
     }
 
     public SearchUiManager getSearchUiManager() {
@@ -75,39 +82,55 @@
         return mSearchContainer;
     }
 
-    /** Updates all apps container with the latest search query. */
-    public void setLastSearchQuery(String query) {
-        Intent marketSearchIntent = PackageManagerHelper.getMarketSearchIntent(
-                mActivityContext, query);
-        OnClickListener marketSearchClickListener = (v) -> mActivityContext.startActivitySafely(v,
-                marketSearchIntent, null);
-        for (int i = 0; i < mAH.size(); i++) {
-            mAH.get(i).mAdapter.setLastSearchQuery(query, marketSearchClickListener);
-        }
-        mIsSearching = true;
-        rebindAdapters();
-        mHeader.setCollapsed(true);
-    }
-
     /** Invoke when the current search session is finished. */
     public void onClearSearchResult() {
-        mIsSearching = false;
-        mHeader.setCollapsed(false);
+        getMainAdapterProvider().clearHighlightedItem();
+        animateToSearchState(false);
         rebindAdapters();
-        mHeader.reset(false);
     }
 
     /**
      * Sets results list for search
      */
     public void setSearchResults(ArrayList<AdapterItem> results) {
+        getMainAdapterProvider().clearHighlightedItem();
         if (getSearchResultList().setSearchResults(results)) {
-            for (int i = 0; i < mAH.size(); i++) {
-                if (mAH.get(i).mRecyclerView != null) {
-                    mAH.get(i).mRecyclerView.onSearchResultsChanged();
-                }
-            }
+            getSearchRecyclerView().onSearchResultsChanged();
         }
+        if (results != null) {
+            animateToSearchState(true);
+        }
+    }
+
+    private void animateToSearchState(boolean goingToSearch) {
+        animateToSearchState(goingToSearch, DEFAULT_SEARCH_TRANSITION_DURATION_MS);
+    }
+
+    private void animateToSearchState(boolean goingToSearch, long durationMs) {
+        if (!mSearchTransitionController.isRunning() && goingToSearch == isSearching()) {
+            return;
+        }
+        if (goingToSearch) {
+            // Fade out the button to pause work apps.
+            mWorkManager.onActivePageChanged(SEARCH);
+        }
+        mSearchTransitionController.animateToSearchState(goingToSearch, durationMs,
+                /* onEndRunnable = */ () -> {
+                    mIsSearching = goingToSearch;
+                    updateSearchResultsVisibility();
+                    int previousPage = getCurrentPage();
+                    if (mRebindAdaptersAfterSearchAnimation) {
+                        rebindAdapters(false);
+                        mRebindAdaptersAfterSearchAnimation = false;
+                    }
+                    if (!goingToSearch) {
+                        setSearchResults(null);
+                        if (mViewPager != null) {
+                            mViewPager.setCurrentPage(previousPage);
+                        }
+                        onActivePageChanged(previousPage);
+                    }
+                });
     }
 
     @Override
@@ -130,6 +153,8 @@
         super.reset(animate);
         // Reset the search bar after transitioning home.
         mSearchUiManager.resetSearch();
+        // Animate to A-Z with 0 time to reset the animation with proper state management.
+        animateToSearchState(false, 0);
     }
 
     @Override
@@ -156,31 +181,35 @@
     }
 
     @Override
-    protected boolean shouldShowTabs() {
-        return super.shouldShowTabs() && !isSearching();
-    }
-
-    @Override
     public boolean isSearching() {
         return mIsSearching;
     }
 
     @Override
+    public void onActivePageChanged(int currentActivePage) {
+        if (mSearchTransitionController.isRunning()) {
+            // Will be called at the end of the animation.
+            return;
+        }
+        super.onActivePageChanged(currentActivePage);
+    }
+
+    @Override
     protected void rebindAdapters(boolean force) {
+        if (mSearchTransitionController.isRunning()) {
+            mRebindAdaptersAfterSearchAnimation = true;
+            return;
+        }
         super.rebindAdapters(force);
         if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get()
-                || getMainAdapterProvider().getDecorator() == null) {
+                || getMainAdapterProvider().getDecorator() == null
+                || getSearchRecyclerView() == null) {
             return;
         }
 
         RecyclerView.ItemDecoration decoration = getMainAdapterProvider().getDecorator();
-        mAH.stream()
-                .map(adapterHolder -> adapterHolder.mRecyclerView)
-                .filter(Objects::nonNull)
-                .forEach(v -> {
-                    v.removeItemDecoration(decoration); // Remove in case it is already added.
-                    v.addItemDecoration(decoration);
-                });
+        getSearchRecyclerView().removeItemDecoration(decoration); // In case it is already added.
+        getSearchRecyclerView().addItemDecoration(decoration);
     }
 
     @Override
diff --git a/src/com/android/launcher3/allapps/AllAppsBackgroundDrawable.java b/src/com/android/launcher3/allapps/AllAppsBackgroundDrawable.java
deleted file mode 100644
index 3830a93..0000000
--- a/src/com/android/launcher3/allapps/AllAppsBackgroundDrawable.java
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.allapps;
-
-import android.animation.ObjectAnimator;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.view.ContextThemeWrapper;
-import android.view.Gravity;
-
-import com.android.launcher3.LauncherAnimUtils;
-import com.android.launcher3.R;
-import com.android.launcher3.util.Themes;
-
-/**
- * This is a custom composite drawable that has a fixed virtual size and dynamically lays out its
- * children images relatively within its bounds.  This way, we can reduce the memory usage of a
- * single, large sparsely populated image.
- */
-public class AllAppsBackgroundDrawable extends Drawable {
-
-    /**
-     * A helper class to position and orient a drawable to be drawn.
-     */
-    protected static class TransformedImageDrawable {
-        private Drawable mImage;
-        private float mXPercent;
-        private float mYPercent;
-        private int mGravity;
-        private int mAlpha;
-
-        /**
-         * @param gravity If one of the Gravity center values, the x and y offset will take the width
-         *                and height of the image into account to center the image to the offset.
-         */
-        public TransformedImageDrawable(Context context, int resourceId, float xPct, float yPct,
-                int gravity) {
-            mImage = context.getDrawable(resourceId);
-            mXPercent = xPct;
-            mYPercent = yPct;
-            mGravity = gravity;
-        }
-
-        public void setAlpha(int alpha) {
-            mImage.setAlpha(alpha);
-            mAlpha = alpha;
-        }
-
-        public int getAlpha() {
-            return mAlpha;
-        }
-
-        public void updateBounds(Rect bounds) {
-            int width = mImage.getIntrinsicWidth();
-            int height = mImage.getIntrinsicHeight();
-            int left = bounds.left + (int) (mXPercent * bounds.width());
-            int top = bounds.top + (int) (mYPercent * bounds.height());
-            if ((mGravity & Gravity.CENTER_HORIZONTAL) == Gravity.CENTER_HORIZONTAL) {
-                left -= (width / 2);
-            }
-            if ((mGravity & Gravity.CENTER_VERTICAL) == Gravity.CENTER_VERTICAL) {
-                top -= (height / 2);
-            }
-            mImage.setBounds(left, top, left + width, top + height);
-        }
-
-        public void draw(Canvas canvas) {
-            mImage.draw(canvas);
-        }
-
-        public Rect getBounds() {
-            return mImage.getBounds();
-        }
-    }
-
-    protected final TransformedImageDrawable mHand;
-    protected final TransformedImageDrawable[] mIcons;
-    private final int mWidth;
-    private final int mHeight;
-
-    private ObjectAnimator mBackgroundAnim;
-
-    public AllAppsBackgroundDrawable(Context context) {
-        Resources res = context.getResources();
-        mWidth = res.getDimensionPixelSize(R.dimen.all_apps_background_canvas_width);
-        mHeight = res.getDimensionPixelSize(R.dimen.all_apps_background_canvas_height);
-
-        context = new ContextThemeWrapper(context,
-                Themes.getAttrBoolean(context, R.attr.isMainColorDark)
-                        ? R.style.AllAppsEmptySearchBackground_Dark
-                        : R.style.AllAppsEmptySearchBackground);
-        mHand = new TransformedImageDrawable(context, R.drawable.ic_all_apps_bg_hand,
-                0.575f, 0.f, Gravity.CENTER_HORIZONTAL);
-        mIcons = new TransformedImageDrawable[4];
-        mIcons[0] = new TransformedImageDrawable(context, R.drawable.ic_all_apps_bg_icon_1,
-                0.375f, 0, Gravity.CENTER_HORIZONTAL);
-        mIcons[1] = new TransformedImageDrawable(context, R.drawable.ic_all_apps_bg_icon_2,
-                0.3125f, 0.2f, Gravity.CENTER_HORIZONTAL);
-        mIcons[2] = new TransformedImageDrawable(context, R.drawable.ic_all_apps_bg_icon_3,
-                0.475f, 0.26f, Gravity.CENTER_HORIZONTAL);
-        mIcons[3] = new TransformedImageDrawable(context, R.drawable.ic_all_apps_bg_icon_4,
-                0.7f, 0.125f, Gravity.CENTER_HORIZONTAL);
-    }
-
-    /**
-     * Animates the background alpha.
-     */
-    public void animateBgAlpha(float finalAlpha, int duration) {
-        int finalAlphaI = (int) (finalAlpha * 255f);
-        if (getAlpha() != finalAlphaI) {
-            mBackgroundAnim = cancelAnimator(mBackgroundAnim);
-            mBackgroundAnim = ObjectAnimator.ofInt(this, LauncherAnimUtils.DRAWABLE_ALPHA,
-                    finalAlphaI);
-            mBackgroundAnim.setDuration(duration);
-            mBackgroundAnim.start();
-        }
-    }
-
-    /**
-     * Sets the background alpha immediately.
-     */
-    public void setBgAlpha(float finalAlpha) {
-        int finalAlphaI = (int) (finalAlpha * 255f);
-        if (getAlpha() != finalAlphaI) {
-            mBackgroundAnim = cancelAnimator(mBackgroundAnim);
-            setAlpha(finalAlphaI);
-        }
-    }
-
-    @Override
-    public int getIntrinsicWidth() {
-        return mWidth;
-    }
-
-    @Override
-    public int getIntrinsicHeight() {
-        return mHeight;
-    }
-
-    @Override
-    public void draw(Canvas canvas) {
-        mHand.draw(canvas);
-        for (int i = 0; i < mIcons.length; i++) {
-            mIcons[i].draw(canvas);
-        }
-    }
-
-    @Override
-    protected void onBoundsChange(Rect bounds) {
-        super.onBoundsChange(bounds);
-        mHand.updateBounds(bounds);
-        for (int i = 0; i < mIcons.length; i++) {
-            mIcons[i].updateBounds(bounds);
-        }
-        invalidateSelf();
-    }
-
-    @Override
-    public void setAlpha(int alpha) {
-        mHand.setAlpha(alpha);
-        for (int i = 0; i < mIcons.length; i++) {
-            mIcons[i].setAlpha(alpha);
-        }
-        invalidateSelf();
-    }
-
-    @Override
-    public int getAlpha() {
-        return mHand.getAlpha();
-    }
-
-    @Override
-    public void setColorFilter(ColorFilter colorFilter) {
-        // Do nothing
-    }
-
-    @Override
-    public int getOpacity() {
-        return PixelFormat.TRANSLUCENT;
-    }
-
-    private ObjectAnimator cancelAnimator(ObjectAnimator animator) {
-        if (animator != null) {
-            animator.cancel();
-        }
-        return null;
-    }
-}
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 33d2f2b..e0b4951 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -26,7 +26,9 @@
 import androidx.core.view.accessibility.AccessibilityRecordCompat;
 import androidx.recyclerview.widget.GridLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.Adapter;
 
+import com.android.launcher3.util.ScrollableLayoutManager;
 import com.android.launcher3.views.ActivityContext;
 
 import java.util.List;
@@ -62,10 +64,10 @@
     /**
      * A subclass of GridLayoutManager that overrides accessibility values during app search.
      */
-    public class AppsGridLayoutManager extends GridLayoutManager {
+    public class AppsGridLayoutManager extends ScrollableLayoutManager {
 
         public AppsGridLayoutManager(Context context) {
-            super(context, 1, GridLayoutManager.VERTICAL, false);
+            super(context);
         }
 
         @Override
@@ -125,6 +127,15 @@
             }
             return extraRows;
         }
+
+        @Override
+        protected int incrementTotalHeight(Adapter adapter, int position, int heightUntilLastPos) {
+            AllAppsGridAdapter.AdapterItem item = mApps.getAdapterItems().get(position);
+            // only account for the first icon in the row since they are the same size within a row
+            return (isIconViewType(item.viewType) && item.rowAppIndex != 0)
+                    ? heightUntilLastPos
+                    : (heightUntilLastPos + mCachedSizes.get(item.viewType));
+        }
     }
 
     @Override
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index af17cf7..3d06fb5 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -15,23 +15,15 @@
  */
 package com.android.launcher3.allapps;
 
-import static android.view.View.MeasureSpec.UNSPECIFIED;
-
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_SCROLLED;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_VERTICAL_SWIPE_BEGIN;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_VERTICAL_SWIPE_END;
 import static com.android.launcher3.util.LogConfig.SEARCH_LOGGING;
-import static com.android.launcher3.util.UiThreadHelper.hideKeyboardAsync;
 
 import android.content.Context;
-import android.content.res.Resources;
 import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.util.Log;
-import android.util.SparseIntArray;
-import android.view.MotionEvent;
-import android.view.View;
 
 import androidx.recyclerview.widget.RecyclerView;
 
@@ -40,7 +32,6 @@
 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.logging.StatsLogManager;
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.views.RecyclerViewFastScroller;
@@ -55,44 +46,10 @@
     private static final boolean DEBUG = false;
     private static final boolean DEBUG_LATENCY = Utilities.isPropertyEnabled(SEARCH_LOGGING);
 
-    protected AlphabeticalAppsList<?> mApps;
     protected final int mNumAppsPerRow;
-
-    // The specific view heights that we use to calculate scroll
-    private final SparseIntArray mViewHeights = new SparseIntArray();
-    private final SparseIntArray mCachedScrollPositions = new SparseIntArray();
     private final AllAppsFastScrollHelper mFastScrollHelper;
 
-
-    private final AdapterDataObserver mObserver = new RecyclerView.AdapterDataObserver() {
-        public void onChanged() {
-            mCachedScrollPositions.clear();
-        }
-
-        @Override
-        public void onItemRangeChanged(int positionStart, int itemCount) {
-            onChanged();
-        }
-
-        @Override
-        public void onItemRangeInserted(int positionStart, int itemCount) {
-            onChanged();
-        }
-
-        @Override
-        public void onItemRangeRemoved(int positionStart, int itemCount) {
-            onChanged();
-        }
-
-        @Override
-        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
-            onChanged();
-        }
-    };
-
-    // The empty-search result background
-    protected AllAppsBackgroundDrawable mEmptySearchBackground;
-    protected int mEmptySearchBackgroundTopOffset;
+    protected AlphabeticalAppsList<?> mApps;
 
     public AllAppsRecyclerView(Context context) {
         this(context, null);
@@ -109,9 +66,6 @@
     public AllAppsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr);
-        Resources res = getResources();
-        mEmptySearchBackgroundTopOffset = res.getDimensionPixelSize(
-                R.dimen.all_apps_empty_search_bg_top_offset);
         mNumAppsPerRow = LauncherAppState.getIDP(context).numColumns;
         mFastScrollHelper = new AllAppsFastScrollHelper(this);
     }
@@ -133,21 +87,12 @@
         int approxRows = (int) Math.ceil(grid.availableHeightPx / grid.allAppsIconSizePx);
         pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH, 1);
         pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_ALL_APPS_DIVIDER, 1);
-        pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET, 1);
         pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_ICON, approxRows
                 * (mNumAppsPerRow + 1));
-
-        mViewHeights.clear();
-        mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_ICON, grid.allAppsCellHeightPx);
     }
 
-
     @Override
     public void onDraw(Canvas c) {
-        // Draw the background
-        if (mEmptySearchBackground != null && mEmptySearchBackground.getAlpha() > 0) {
-            mEmptySearchBackground.draw(c);
-        }
         if (DEBUG) {
             Log.d(TAG, "onDraw at = " + System.currentTimeMillis());
         }
@@ -159,33 +104,13 @@
     }
 
     @Override
-    protected boolean verifyDrawable(Drawable who) {
-        return who == mEmptySearchBackground || super.verifyDrawable(who);
-    }
-
-    @Override
     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
-        updateEmptySearchBackgroundBounds();
         updatePoolSize();
     }
 
     public void onSearchResultsChanged() {
         // Always scroll the view to the top so the user can see the changed results
         scrollToTop();
-
-        if (mApps.hasNoFilteredResults() && !FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
-            if (mEmptySearchBackground == null) {
-                mEmptySearchBackground = new AllAppsBackgroundDrawable(getContext());
-                mEmptySearchBackground.setAlpha(0);
-                mEmptySearchBackground.setCallback(this);
-                updateEmptySearchBackgroundBounds();
-            }
-            mEmptySearchBackground.animateBgAlpha(1f, 150);
-        } else if (mEmptySearchBackground != null) {
-            // For the time being, we just immediately hide the background to ensure that it does
-            // not overlap with the results
-            mEmptySearchBackground.setBgAlpha(0f);
-        }
     }
 
     @Override
@@ -199,8 +124,7 @@
                 requestFocus();
                 mgr.logger().sendToInteractionJankMonitor(
                         LAUNCHER_ALLAPPS_VERTICAL_SWIPE_BEGIN, this);
-                hideKeyboardAsync(ActivityContext.lookupContext(getContext()),
-                        getApplicationWindowToken());
+                ActivityContext.lookupContext(getContext()).hideKeyboard();
                 break;
             case SCROLL_STATE_IDLE:
                 mgr.logger().sendToInteractionJankMonitor(
@@ -209,16 +133,6 @@
         }
     }
 
-    @Override
-    public boolean onInterceptTouchEvent(MotionEvent e) {
-        boolean result = super.onInterceptTouchEvent(e);
-        if (!result && e.getAction() == MotionEvent.ACTION_DOWN
-                && mEmptySearchBackground != null && mEmptySearchBackground.getAlpha() > 0) {
-            mEmptySearchBackground.setHotspot(e.getX(), e.getY());
-        }
-        return result;
-    }
-
     /**
      * Maps the touch (from 0..1) to the adapter position that should be visible.
      */
@@ -249,17 +163,6 @@
     }
 
     @Override
-    public void setAdapter(Adapter adapter) {
-        if (getAdapter() != null) {
-            getAdapter().unregisterAdapterDataObserver(mObserver);
-        }
-        super.setAdapter(adapter);
-        if (adapter != null) {
-            adapter.registerAdapterDataObserver(mObserver);
-        }
-    }
-
-    @Override
     protected boolean isPaddingOffsetRequired() {
         return true;
     }
@@ -280,13 +183,13 @@
         List<AllAppsGridAdapter.AdapterItem> items = mApps.getAdapterItems();
 
         // Skip early if there are no items or we haven't been measured
-        if (items.isEmpty() || mNumAppsPerRow == 0) {
+        if (items.isEmpty() || mNumAppsPerRow == 0 || getChildCount() == 0) {
             mScrollbar.setThumbOffsetY(-1);
             return;
         }
 
         // Skip early if, there no child laid out in the container.
-        int scrollY = getCurrentScrollY();
+        int scrollY = computeVerticalScrollOffset();
         if (scrollY < 0) {
             mScrollbar.setThumbOffsetY(-1);
             return;
@@ -341,78 +244,6 @@
         }
     }
 
-    @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) {
-        List<AllAppsGridAdapter.AdapterItem> items = mApps.getAdapterItems();
-        AllAppsGridAdapter.AdapterItem posItem = position < items.size()
-                ? items.get(position) : null;
-        int y = mCachedScrollPositions.get(position, -1);
-        if (y < 0) {
-            y = 0;
-            for (int i = 0; i < position; i++) {
-                AllAppsGridAdapter.AdapterItem item = items.get(i);
-                if (AllAppsGridAdapter.isIconViewType(item.viewType)) {
-                    // Break once we reach the desired row
-                    if (posItem != null && posItem.viewType == item.viewType &&
-                            posItem.rowIndex == item.rowIndex) {
-                        break;
-                    }
-                    // Otherwise, only account for the first icon in the row since they are the same
-                    // size within a row
-                    if (item.rowAppIndex == 0) {
-                        y += mViewHeights.get(item.viewType, 0);
-                    }
-                } else {
-                    // Rest of the views span the full width
-                    int elHeight = mViewHeights.get(item.viewType);
-                    if (elHeight == 0) {
-                        ViewHolder holder = findViewHolderForAdapterPosition(i);
-                        if (holder == null) {
-                            holder = getAdapter().createViewHolder(this, item.viewType);
-                            getAdapter().onBindViewHolder(holder, i);
-                            holder.itemView.measure(UNSPECIFIED, UNSPECIFIED);
-                            elHeight = holder.itemView.getMeasuredHeight();
-
-                            getRecycledViewPool().putRecycledView(holder);
-                        } else {
-                            elHeight = holder.itemView.getMeasuredHeight();
-                        }
-                    }
-                    y += elHeight;
-                }
-            }
-            mCachedScrollPositions.put(position, y);
-        }
-        return y - 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();
-    }
-
     public int getScrollBarTop() {
         return getResources().getDimensionPixelOffset(R.dimen.all_apps_header_top_padding);
     }
@@ -421,22 +252,6 @@
         return mScrollbar;
     }
 
-    /**
-     * Updates the bounds of the empty search background.
-     */
-    private void updateEmptySearchBackgroundBounds() {
-        if (mEmptySearchBackground == null) {
-            return;
-        }
-
-        // Center the empty search background on this new view bounds
-        int x = (getMeasuredWidth() - mEmptySearchBackground.getIntrinsicWidth()) / 2;
-        int y = mEmptySearchBackgroundTopOffset;
-        mEmptySearchBackground.setBounds(x, y,
-                x + mEmptySearchBackground.getIntrinsicWidth(),
-                y + mEmptySearchBackground.getIntrinsicHeight());
-    }
-
     @Override
     public boolean hasOverlappingRendering() {
         return false;
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index a4a2085..872c4fd 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,16 +36,14 @@
 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;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.statemanager.StateManager.StateHandler;
 import com.android.launcher3.states.StateAnimationConfig;
-import com.android.launcher3.util.MultiAdditivePropertyFactory;
+import com.android.launcher3.util.MultiPropertyFactory;
 import com.android.launcher3.util.MultiValueAlpha;
-import com.android.launcher3.util.UiThreadHelper;
 import com.android.launcher3.views.ScrimView;
 
 /**
@@ -144,9 +141,9 @@
 
     private ScrimView mScrimView;
 
-    private final MultiAdditivePropertyFactory<View>
-            mAppsViewTranslationYPropertyFactory = new MultiAdditivePropertyFactory<>(
-            "appsViewTranslationY", View.TRANSLATION_Y);
+    private final MultiPropertyFactory<View>
+            mAppsViewTranslationYPropertyFactory = new MultiPropertyFactory<>(
+            "appsViewTranslationY", View.TRANSLATION_Y, Float::sum);
     private MultiValueAlpha mAppsViewAlpha;
 
     private boolean mIsTablet;
@@ -230,12 +227,25 @@
     @Override
     public void setStateWithAnimation(LauncherState toState,
             StateAnimationConfig config, PendingAnimation builder) {
-        if (NORMAL.equals(toState) && mLauncher.isInState(ALL_APPS)) {
-            UiThreadHelper.hideKeyboardAsync(mLauncher, mLauncher.getAppsView().getWindowToken());
+        if (mLauncher.isInState(ALL_APPS) && !ALL_APPS.equals(toState)) {
+            // For atomic animations, we close the keyboard immediately.
+            if (!config.userControlled && !FeatureFlags.ENABLE_KEYBOARD_TRANSITION_SYNC.get()) {
+                mLauncher.getAppsView().getSearchUiManager().getEditText().hideKeyboard();
+            }
+
             builder.addEndListener(success -> {
                 // Reset pull back progress and alpha after switching states.
                 ALL_APPS_PULL_BACK_TRANSLATION.set(this, 0f);
                 ALL_APPS_PULL_BACK_ALPHA.set(this, 1f);
+
+                // We only want to close the keyboard if the animation has completed successfully.
+                // The reason is that with keyboard sync, if the user swipes down from All Apps with
+                // the keyboard open and then changes their mind and swipes back up, we want the
+                // keyboard to remain open. However an onCancel signal is sent to the listeners
+                // (success = false), so we need to check for that.
+                if (config.userControlled && success) {
+                    mLauncher.getAppsView().getSearchUiManager().getEditText().hideKeyboard();
+                }
             });
         }
 
@@ -243,7 +253,6 @@
         if (Float.compare(mProgress, targetProgress) == 0) {
             setAlphas(toState, config, builder);
             // Fail fast
-            onProgressAnimationEnd();
             return;
         }
 
@@ -293,11 +302,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);
@@ -315,9 +319,9 @@
      * TODO: This logic should go in {@link LauncherState}
      */
     private void onProgressAnimationEnd() {
-        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) return;
         if (Float.compare(mProgress, 1f) == 0) {
             mAppsView.reset(false /* animate */);
+            mLauncher.getAppsView().getSearchUiManager().getEditText().hideKeyboard();
         }
     }
 }
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 45a567d..fc1b830 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -15,17 +15,12 @@
  */
 package com.android.launcher3.allapps;
 
-import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_ALL_APPS_DIVIDER;
-import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_EMPTY_SEARCH;
-import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_SEARCH_MARKET;
-
 import android.content.Context;
 
 import androidx.annotation.Nullable;
 import androidx.recyclerview.widget.DiffUtil;
 
 import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.util.LabelComparator;
@@ -50,7 +45,7 @@
 
     public static final String TAG = "AlphabeticalAppsList";
 
-    private final WorkAdapterProvider mWorkAdapterProvider;
+    private final WorkProfileManager mWorkProviderManager;
 
     /**
      * Info about a fast scroller section, depending if sections are merged, the fast scroller
@@ -92,11 +87,11 @@
     private Predicate<ItemInfo> mItemFilter;
 
     public AlphabeticalAppsList(Context context, @Nullable AllAppsStore appsStore,
-            WorkAdapterProvider adapterProvider) {
+            WorkProfileManager workProfileManager) {
         mAllAppsStore = appsStore;
         mActivityContext = ActivityContext.lookupContext(context);
         mAppNameComparator = new AppInfoComparator(context);
-        mWorkAdapterProvider = adapterProvider;
+        mWorkProviderManager = workProfileManager;
         mNumAppsPerRowAllApps = mActivityContext.getDeviceProfile().inv.numAllAppsColumns;
         if (mAllAppsStore != null) {
             mAllAppsStore.addUpdateListener(this);
@@ -173,13 +168,6 @@
     }
 
     /**
-     * Returns whether there are no filtered results.
-     */
-    public boolean hasNoFilteredResults() {
-        return hasSearchResults() && mAccessibilityResultsCount == 0;
-    }
-
-    /**
      * Sets results list for search
      */
     public boolean setSearchResults(ArrayList<AdapterItem> results) {
@@ -249,20 +237,11 @@
         // ordered set of sections
         if (hasSearchResults()) {
             mAdapterItems.addAll(mSearchResults);
-            if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
-                // Append the search market item
-                if (hasNoFilteredResults()) {
-                    mAdapterItems.add(new AdapterItem(VIEW_TYPE_EMPTY_SEARCH));
-                } else {
-                    mAdapterItems.add(new AdapterItem(VIEW_TYPE_ALL_APPS_DIVIDER));
-                }
-                mAdapterItems.add(new AdapterItem(VIEW_TYPE_SEARCH_MARKET));
-            }
         } else {
             int position = 0;
-            if (mWorkAdapterProvider != null) {
-                position += mWorkAdapterProvider.addWorkItems(mAdapterItems);
-                if (!mWorkAdapterProvider.shouldShowWorkApps()) {
+            if (mWorkProviderManager != null) {
+                position += mWorkProviderManager.addWorkItems(mAdapterItems);
+                if (!mWorkProviderManager.shouldShowWorkApps()) {
                     return;
                 }
             }
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
index fcba246..42f8b0c 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
@@ -19,7 +19,6 @@
 
 import android.content.Context;
 import android.content.res.Resources;
-import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.View.OnClickListener;
@@ -54,14 +53,13 @@
     public static final int VIEW_TYPE_ICON = 1 << 1;
     // The message shown when there are no filtered results
     public static final int VIEW_TYPE_EMPTY_SEARCH = 1 << 2;
-    // The message to continue to a market search when there are no filtered results
-    public static final int VIEW_TYPE_SEARCH_MARKET = 1 << 3;
-
-    // We use various dividers for various purposes.  They share enough attributes to reuse layouts,
-    // but differ in enough attributes to require different view types
-
     // A divider that separates the apps list and the search market button
-    public static final int VIEW_TYPE_ALL_APPS_DIVIDER = 1 << 4;
+    public static final int VIEW_TYPE_ALL_APPS_DIVIDER = 1 << 3;
+
+    public static final int VIEW_TYPE_WORK_EDU_CARD = 1 << 4;
+    public static final int VIEW_TYPE_WORK_DISABLED_CARD = 1 << 5;
+
+    public static final int NEXT_ID = 6;
 
     // Common view type masks
     public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_ALL_APPS_DIVIDER;
@@ -112,7 +110,7 @@
         }
 
         protected boolean isCountedForAccessibility() {
-            return viewType == VIEW_TYPE_ICON || viewType == VIEW_TYPE_SEARCH_MARKET;
+            return viewType == VIEW_TYPE_ICON;
         }
 
         /**
@@ -129,20 +127,22 @@
         public boolean isContentSame(AdapterItem other) {
             return itemInfo == null && other.itemInfo == null;
         }
+
+        /** Sets the alpha of the decorator for this item. Returns true if successful. */
+        public boolean setDecorationFillAlpha(int alpha) {
+            return false;
+        }
     }
 
     protected final T mActivityContext;
     protected final AlphabeticalAppsList<T> mApps;
     // The text to show when there are no search results and no market search handler.
-    protected String mEmptySearchMessage;
     protected int mAppsPerRow;
 
     protected final LayoutInflater mLayoutInflater;
     protected final OnClickListener mOnIconClickListener;
     protected OnLongClickListener mOnIconLongClickListener = INSTANCE_ALL_APPS;
     protected OnFocusChangeListener mIconFocusListener;
-    // The click listener to send off to the market app, updated each time the search query changes.
-    private OnClickListener mMarketSearchClickListener;
     private final int mExtraHeight;
 
     public BaseAllAppsAdapter(T activityContext, LayoutInflater inflater,
@@ -150,7 +150,6 @@
         Resources res = activityContext.getResources();
         mActivityContext = activityContext;
         mApps = apps;
-        mEmptySearchMessage = res.getString(R.string.all_apps_loading_message);
         mLayoutInflater = inflater;
 
         mOnIconClickListener = mActivityContext.getItemOnClickListener();
@@ -181,16 +180,6 @@
     }
 
     /**
-     * Sets the last search query that was made, used to show when there are no results and to also
-     * seed the intent for searching the market.
-     */
-    public void setLastSearchQuery(String query, OnClickListener marketSearchClickListener) {
-        Resources res = mActivityContext.getResources();
-        mEmptySearchMessage = res.getString(R.string.all_apps_no_search_results, query);
-        mMarketSearchClickListener = marketSearchClickListener;
-    }
-
-    /**
      * Returns the layout manager.
      */
     public abstract RecyclerView.LayoutManager getLayoutManager();
@@ -217,14 +206,15 @@
             case VIEW_TYPE_EMPTY_SEARCH:
                 return new ViewHolder(mLayoutInflater.inflate(R.layout.all_apps_empty_search,
                         parent, false));
-            case VIEW_TYPE_SEARCH_MARKET:
-                View searchMarketView = mLayoutInflater.inflate(R.layout.all_apps_search_market,
-                        parent, false);
-                searchMarketView.setOnClickListener(mMarketSearchClickListener);
-                return new ViewHolder(searchMarketView);
             case VIEW_TYPE_ALL_APPS_DIVIDER:
                 return new ViewHolder(mLayoutInflater.inflate(
                         R.layout.all_apps_divider, parent, false));
+            case VIEW_TYPE_WORK_EDU_CARD:
+                return new ViewHolder(mLayoutInflater.inflate(
+                        R.layout.work_apps_edu, parent, false));
+            case VIEW_TYPE_WORK_DISABLED_CARD:
+                return new ViewHolder(mLayoutInflater.inflate(
+                        R.layout.work_apps_paused, parent, false));
             default:
                 BaseAdapterProvider adapterProvider = getAdapterProvider(viewType);
                 if (adapterProvider != null) {
@@ -237,29 +227,28 @@
     @Override
     public void onBindViewHolder(ViewHolder holder, int position) {
         switch (holder.getItemViewType()) {
-            case VIEW_TYPE_ICON:
+            case VIEW_TYPE_ICON: {
                 AdapterItem adapterItem = mApps.getAdapterItems().get(position);
                 BubbleTextView icon = (BubbleTextView) holder.itemView;
                 icon.reset();
                 icon.applyFromApplicationInfo(adapterItem.itemInfo);
                 break;
-            case VIEW_TYPE_EMPTY_SEARCH:
-                TextView emptyViewText = (TextView) holder.itemView;
-                emptyViewText.setText(mEmptySearchMessage);
-                emptyViewText.setGravity(mApps.hasNoFilteredResults() ? Gravity.CENTER :
-                        Gravity.START | Gravity.CENTER_VERTICAL);
-                break;
-            case VIEW_TYPE_SEARCH_MARKET:
-                TextView searchView = (TextView) holder.itemView;
-                if (mMarketSearchClickListener != null) {
-                    searchView.setVisibility(View.VISIBLE);
-                } else {
-                    searchView.setVisibility(View.GONE);
+            }
+            case VIEW_TYPE_EMPTY_SEARCH: {
+                AppInfo info = mApps.getAdapterItems().get(position).itemInfo;
+                if (info != null) {
+                    ((TextView) holder.itemView).setText(mActivityContext.getString(
+                            R.string.all_apps_no_search_results, info.title));
                 }
                 break;
+            }
             case VIEW_TYPE_ALL_APPS_DIVIDER:
+            case VIEW_TYPE_WORK_DISABLED_CARD:
                 // nothing to do
                 break;
+            case VIEW_TYPE_WORK_EDU_CARD:
+                ((WorkEduCard) holder.itemView).setPosition(position);
+                break;
             default:
                 BaseAdapterProvider adapterProvider = getAdapterProvider(holder.getItemViewType());
                 if (adapterProvider != null) {
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
index ecadec6..f082542 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
@@ -17,7 +17,6 @@
 
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_TAP_ON_PERSONAL_TAB;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_TAP_ON_WORK_TAB;
-import static com.android.launcher3.util.UiThreadHelper.hideKeyboardAsync;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -89,6 +88,9 @@
     public static final float PULL_MULTIPLIER = .02f;
     public static final float FLING_VELOCITY_MULTIPLIER = 1200f;
 
+    // Render the header protection at all times to debug clipping issues.
+    private static final boolean DEBUG_HEADER_PROTECTION = false;
+
     private final Paint mHeaderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
     private final Rect mInsets = new Rect();
 
@@ -104,15 +106,17 @@
             new RecyclerView.OnScrollListener() {
                 @Override
                 public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
-                    updateHeaderScroll(((AllAppsRecyclerView) recyclerView).getCurrentScrollY());
+                    updateHeaderScroll(
+                            ((AllAppsRecyclerView) recyclerView).computeVerticalScrollOffset());
                 }
             };
-    private final WorkProfileManager mWorkManager;
+
+    protected final WorkProfileManager mWorkManager;
 
     private final Paint mNavBarScrimPaint;
     private int mNavBarScrimHeight = 0;
 
-    private AllAppsPagedView mViewPager;
+    protected AllAppsPagedView mViewPager;
     private SearchRecyclerView mSearchRecyclerView;
 
     protected FloatingHeaderView mHeader;
@@ -128,7 +132,6 @@
     private final int mScrimColor;
     private final int mHeaderProtectionColor;
     protected final float mHeaderThreshold;
-    private int mHeaderBottomAdjustment;
     private ScrimView mScrimView;
     private int mHeaderColor;
     private int mTabsProtectionAlpha;
@@ -141,14 +144,11 @@
         mScrimColor = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
         mHeaderThreshold = getResources().getDimensionPixelSize(
                 R.dimen.dynamic_grid_cell_border_spacing);
-        mHeaderBottomAdjustment = getResources().getDimensionPixelSize(
-                R.dimen.all_apps_header_bottom_adjustment);
         mHeaderProtectionColor = Themes.getAttrColor(context, R.attr.allappsHeaderProtectionColor);
 
         mWorkManager = new WorkProfileManager(
                 mActivityContext.getSystemService(UserManager.class),
-                this,
-                Utilities.getPrefs(mActivityContext), mActivityContext.getDeviceProfile());
+                this, Utilities.getPrefs(mActivityContext));
         mAH = Arrays.asList(null, null, null);
         mAH.set(AdapterHolder.MAIN, new AdapterHolder(AdapterHolder.MAIN));
         mAH.set(AdapterHolder.WORK, new AdapterHolder(AdapterHolder.WORK));
@@ -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;
         }
@@ -349,7 +350,7 @@
      * The container for A-Z apps (the ViewPager for main+work tabs, or main RV). This is currently
      * hidden while searching.
      **/
-    private View getAppsRecyclerViewContainer() {
+    protected View getAppsRecyclerViewContainer() {
         return mViewPager != null ? mViewPager : findViewById(R.id.apps_list_view);
     }
 
@@ -491,6 +492,7 @@
 
         mAllAppsStore.unregisterIconContainer(mAH.get(AdapterHolder.MAIN).mRecyclerView);
         mAllAppsStore.unregisterIconContainer(mAH.get(AdapterHolder.WORK).mRecyclerView);
+        mAllAppsStore.unregisterIconContainer(mAH.get(AdapterHolder.SEARCH).mRecyclerView);
 
         if (mUsingTabs) {
             mAH.get(AdapterHolder.MAIN).setup(mViewPager.getChildAt(0), mPersonalMatcher);
@@ -503,8 +505,7 @@
                             mActivityContext.getStatsLogManager().logger()
                                     .log(LAUNCHER_ALLAPPS_TAP_ON_PERSONAL_TAB);
                         }
-                        hideKeyboardAsync(ActivityContext.lookupContext(getContext()),
-                                getApplicationWindowToken());
+                        mActivityContext.hideKeyboard();
                     });
             findViewById(R.id.tab_work)
                     .setOnClickListener((View view) -> {
@@ -512,8 +513,7 @@
                             mActivityContext.getStatsLogManager().logger()
                                     .log(LAUNCHER_ALLAPPS_TAP_ON_WORK_TAB);
                         }
-                        hideKeyboardAsync(ActivityContext.lookupContext(getContext()),
-                                getApplicationWindowToken());
+                        mActivityContext.hideKeyboard();
                     });
             setDeviceManagementResources();
             onActivePageChanged(mViewPager.getNextPage());
@@ -525,9 +525,10 @@
 
         mAllAppsStore.registerIconContainer(mAH.get(AdapterHolder.MAIN).mRecyclerView);
         mAllAppsStore.registerIconContainer(mAH.get(AdapterHolder.WORK).mRecyclerView);
+        mAllAppsStore.registerIconContainer(mAH.get(AdapterHolder.SEARCH).mRecyclerView);
     }
 
-    private void updateSearchResultsVisibility() {
+    protected void updateSearchResultsVisibility() {
         if (isSearching()) {
             getSearchRecyclerView().setVisibility(VISIBLE);
             getAppsRecyclerViewContainer().setVisibility(GONE);
@@ -602,6 +603,8 @@
         if (mAH.get(currentActivePage).mRecyclerView != null) {
             mAH.get(currentActivePage).mRecyclerView.bindFastScrollbar();
         }
+        // Header keeps track of active recycler view to properly render header protection.
+        mHeader.setActiveRV(currentActivePage);
         reset(true /* animate */);
 
         mWorkManager.onActivePageChanged(currentActivePage);
@@ -725,17 +728,29 @@
         if (!mHeader.isHeaderProtectionSupported()) {
             return;
         }
-        mHeaderPaint.setColor(mHeaderColor);
-        mHeaderPaint.setAlpha((int) (getAlpha() * Color.alpha(mHeaderColor)));
+        if (DEBUG_HEADER_PROTECTION) {
+            mHeaderPaint.setColor(Color.MAGENTA);
+            mHeaderPaint.setAlpha(255);
+        } else {
+            mHeaderPaint.setColor(mHeaderColor);
+            mHeaderPaint.setAlpha((int) (getAlpha() * Color.alpha(mHeaderColor)));
+        }
         if (mHeaderPaint.getColor() != mScrimColor && mHeaderPaint.getColor() != 0) {
             int bottom = getHeaderBottom();
+            FloatingHeaderView headerView = getFloatingHeaderView();
             if (!mUsingTabs) {
-                bottom += getFloatingHeaderView().getPaddingBottom() - mHeaderBottomAdjustment;
+                // Add protection which is otherwise added when tabs scroll up.
+                bottom += headerView.getTabsAdditionalPaddingTop();
             }
             canvas.drawRect(0, 0, canvas.getWidth(), bottom, mHeaderPaint);
-            int tabsHeight = getFloatingHeaderView().getPeripheralProtectionHeight();
+            int tabsHeight = headerView.getPeripheralProtectionHeight();
             if (mTabsProtectionAlpha > 0 && tabsHeight != 0) {
-                mHeaderPaint.setAlpha((int) (getAlpha() * mTabsProtectionAlpha));
+                if (DEBUG_HEADER_PROTECTION) {
+                    mHeaderPaint.setColor(Color.BLUE);
+                    mHeaderPaint.setAlpha(255);
+                } else {
+                    mHeaderPaint.setAlpha((int) (getAlpha() * mTabsProtectionAlpha));
+                }
                 canvas.drawRect(0, bottom, canvas.getWidth(), bottom + tabsHeight, mHeaderPaint);
             }
         }
@@ -799,12 +814,9 @@
             mType = type;
             mAppsList = new AlphabeticalAppsList<>(mActivityContext,
                     isSearch() ? null : mAllAppsStore,
-                    isWork() ? mWorkManager.getAdapterProvider() : null);
-
+                    isWork() ? mWorkManager : null);
             BaseAdapterProvider[] adapterProviders =
-                    isWork() ? new BaseAdapterProvider[]{mMainAdapterProvider,
-                            mWorkManager.getAdapterProvider()}
-                            : new BaseAdapterProvider[]{mMainAdapterProvider};
+                    new BaseAdapterProvider[]{mMainAdapterProvider};
 
             mAdapter = createAdapter(mAppsList, adapterProviders);
             mAppsList.setAdapter(mAdapter);
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index d0331c9..1cbb0f9 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;
@@ -41,6 +42,7 @@
 import com.android.systemui.plugins.PluginListener;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Map;
 
 public class FloatingHeaderView extends LinearLayout implements
@@ -66,7 +68,7 @@
                         mAnimator.cancel();
                     }
 
-                    int current = -mCurrentRV.getCurrentScrollY();
+                    int current = -mCurrentRV.computeVerticalScrollOffset();
                     boolean headerCollapsed = mHeaderCollapsed;
                     moved(current);
                     applyVerticalMove();
@@ -81,16 +83,15 @@
     protected final Map<AllAppsRow, PluginHeaderRow> mPluginRows = new ArrayMap<>();
 
     // 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 final int mTabsAdditionalPaddingTop;
+    private final int mTabsAdditionalPaddingBottom;
+    private boolean mHeaderProtectionSupported;
 
     protected ViewGroup mTabLayout;
     private AllAppsRecyclerView mMainRV;
     private AllAppsRecyclerView mWorkRV;
     private SearchRecyclerView mSearchRV;
     private AllAppsRecyclerView mCurrentRV;
-    public boolean mHeaderCollapsed;
     protected int mSnappedScrolledY;
     private int mTranslationY;
 
@@ -99,7 +100,12 @@
     protected boolean mTabsHidden;
     protected int mMaxTranslation;
 
-    private boolean mCollapsed = false;
+    // Whether the header has been scrolled off-screen.
+    private boolean mHeaderCollapsed;
+    // Whether floating rows like predicted apps are hidden.
+    private boolean mFloatingRowsCollapsed;
+    // Total height of all current floating rows. Collapsed rows == 0 height.
+    private int mFloatingRowsHeight;
 
     // This is initialized once during inflation and stays constant after that. Fixed views
     // cannot be added or removed dynamically.
@@ -116,14 +122,19 @@
 
     public FloatingHeaderView(@NonNull Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
-        mHeaderTopAdjustment = context.getResources()
+        mTabsAdditionalPaddingTop = context.getResources()
                 .getDimensionPixelSize(R.dimen.all_apps_header_top_adjustment);
-        mHeaderBottomAdjustment = context.getResources()
+        mTabsAdditionalPaddingBottom = 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
@@ -142,6 +153,7 @@
         }
         mFixedRows = rows.toArray(new FloatingHeaderRow[rows.size()]);
         mAllRows = mFixedRows;
+        updateFloatingRowsHeight();
     }
 
     @Override
@@ -173,6 +185,7 @@
                 count++;
             }
         }
+        updateFloatingRowsHeight();
     }
 
     @Override
@@ -189,7 +202,7 @@
         int oldMaxHeight = mMaxTranslation;
         updateExpectedHeight();
 
-        if (mMaxTranslation != oldMaxHeight || mCollapsed) {
+        if (mMaxTranslation != oldMaxHeight || mFloatingRowsCollapsed) {
             BaseAllAppsContainerView<?> parent = (BaseAllAppsContainerView<?>) getParent();
             if (parent != null) {
                 parent.setupHeader();
@@ -252,20 +265,20 @@
     }
 
     private void updateExpectedHeight() {
+        updateFloatingRowsHeight();
         mMaxTranslation = 0;
-        if (mCollapsed) {
+        if (mFloatingRowsCollapsed) {
             return;
         }
-        for (FloatingHeaderRow row : mAllRows) {
-            mMaxTranslation += row.getExpectedHeight();
-        }
+        mMaxTranslation += mFloatingRowsHeight;
         if (!mTabsHidden) {
-            mMaxTranslation += mHeaderBottomAdjustment;
+            mMaxTranslation += mTabsAdditionalPaddingBottom
+                    + getResources().getDimensionPixelSize(R.dimen.all_apps_tabs_margin_top);
         }
     }
 
-    public int getMaxTranslation() {
-        if (mMaxTranslation == 0 && mTabsHidden) {
+    int getMaxTranslation() {
+        if (mMaxTranslation == 0 && (mTabsHidden || mFloatingRowsCollapsed)) {
             return getResources().getDimensionPixelSize(R.dimen.all_apps_search_bar_bottom_padding);
         } else if (mMaxTranslation > 0 && mTabsHidden) {
             return mMaxTranslation + getPaddingTop();
@@ -306,7 +319,7 @@
         int uncappedTranslationY = mTranslationY;
         mTranslationY = Math.max(mTranslationY, -mMaxTranslation);
 
-        if (mCollapsed || uncappedTranslationY < mTranslationY - getPaddingTop()) {
+        if (mFloatingRowsCollapsed || uncappedTranslationY < mTranslationY - getPaddingTop()) {
             // we hide it completely if already capped (for opening search anim)
             for (FloatingHeaderRow row : mAllRows) {
                 row.setVerticalScroll(0, true /* isScrolledOut */);
@@ -319,11 +332,12 @@
 
         mTabLayout.setTranslationY(mTranslationY);
 
-        int clipTop = getPaddingTop() - mHeaderTopAdjustment;
+        int clipTop = getPaddingTop() - mTabsAdditionalPaddingTop;
         if (mTabsHidden) {
-            clipTop += getPaddingBottom() - mHeaderBottomAdjustment;
+            // Add back spacing that is otherwise covered by the tabs.
+            clipTop += mTabsAdditionalPaddingTop;
         }
-        mRVClip.top = mTabsHidden ? clipTop : 0;
+        mRVClip.top = mTabsHidden || mFloatingRowsCollapsed ? clipTop : 0;
         mHeaderClip.top = clipTop;
         // clipping on a draw might cause additional redraw
         setClipBounds(mHeaderClip);
@@ -341,10 +355,12 @@
     /**
      * Hides all the floating rows
      */
-    public void setCollapsed(boolean collapse) {
-        if (mCollapsed == collapse) return;
+    public void setFloatingRowsCollapsed(boolean collapsed) {
+        if (mFloatingRowsCollapsed == collapsed) {
+            return;
+        }
 
-        mCollapsed = collapse;
+        mFloatingRowsCollapsed = collapsed;
         onHeightUpdated();
     }
 
@@ -370,6 +386,34 @@
         return !mHeaderCollapsed;
     }
 
+    /** Returns true if personal/work tabs are currently in use. */
+    public boolean usingTabs() {
+        return !mTabsHidden;
+    }
+
+    ViewGroup getTabLayout() {
+        return mTabLayout;
+    }
+
+    /** Calculates the combined height of any floating rows (e.g. predicted apps, app divider). */
+    private void updateFloatingRowsHeight() {
+        mFloatingRowsHeight =
+                Arrays.stream(mAllRows).mapToInt(FloatingHeaderRow::getExpectedHeight).sum();
+    }
+
+    /** Gets the combined height of any floating rows (e.g. predicted apps, app divider). */
+    int getFloatingRowsHeight() {
+        return mFloatingRowsHeight;
+    }
+
+    int getTabsAdditionalPaddingTop() {
+        return mTabsAdditionalPaddingTop;
+    }
+
+    int getTabsAdditionalPaddingBottom() {
+        return mTabsAdditionalPaddingBottom;
+    }
+
     @Override
     public void onAnimationUpdate(ValueAnimator animation) {
         mTranslationY = (Integer) animation.getAnimatedValue();
@@ -434,16 +478,17 @@
     /**
      * Returns visible height of FloatingHeaderView contents requiring header protection
      */
-    public int getPeripheralProtectionHeight() {
+    int getPeripheralProtectionHeight() {
         if (!mHeaderProtectionSupported) {
             return 0;
         }
 
         // we only want to show protection when work tab is available and header is either
         // collapsed or animating to/from collapsed state
-        if (mTabsHidden || !mHeaderCollapsed) {
+        if (mTabsHidden || mFloatingRowsCollapsed || !mHeaderCollapsed) {
             return 0;
         }
-        return Math.max(getHeight() - getPaddingTop() + mTranslationY, 0);
+        return Math.max(0,
+                getTabLayout().getBottom() - getPaddingTop() + getPaddingBottom() + mTranslationY);
     }
 }
diff --git a/src/com/android/launcher3/allapps/SearchRecyclerView.java b/src/com/android/launcher3/allapps/SearchRecyclerView.java
index 482bd29..9d1dfc0 100644
--- a/src/com/android/launcher3/allapps/SearchRecyclerView.java
+++ b/src/com/android/launcher3/allapps/SearchRecyclerView.java
@@ -17,12 +17,17 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.core.util.Consumer;
 
 import com.android.launcher3.views.RecyclerViewFastScroller;
 
 /** A RecyclerView for AllApps Search results. */
 public class SearchRecyclerView extends AllAppsRecyclerView {
-    private static final String TAG = "SearchRecyclerView";
+
+    private Consumer<View> mChildAttachedConsumer;
 
     public SearchRecyclerView(Context context) {
         this(context, null);
@@ -41,6 +46,11 @@
         super(context, attrs, defStyleAttr, defStyleRes);
     }
 
+    /** This will be called just before a new child is attached to the window. */
+    public void setChildAttachedConsumer(Consumer<View> childAttachedConsumer) {
+        mChildAttachedConsumer = childAttachedConsumer;
+    }
+
     @Override
     protected void updatePoolSize() {
         RecycledViewPool pool = getRecycledViewPool();
@@ -57,4 +67,12 @@
     public RecyclerViewFastScroller getScrollbar() {
         return null;
     }
+
+    @Override
+    public void onChildAttachedToWindow(@NonNull View child) {
+        if (mChildAttachedConsumer != null) {
+            mChildAttachedConsumer.accept(child);
+        }
+        super.onChildAttachedToWindow(child);
+    }
 }
diff --git a/src/com/android/launcher3/allapps/SearchTransitionController.java b/src/com/android/launcher3/allapps/SearchTransitionController.java
new file mode 100644
index 0000000..8fc7965
--- /dev/null
+++ b/src/com/android/launcher3/allapps/SearchTransitionController.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.allapps;
+
+import static android.view.View.VISIBLE;
+
+import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
+
+import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
+import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
+import static com.android.launcher3.anim.Interpolators.INSTANT;
+import static com.android.launcher3.anim.Interpolators.clampToProgress;
+
+import android.animation.ObjectAnimator;
+import android.graphics.drawable.Drawable;
+import android.util.FloatProperty;
+import android.view.View;
+import android.view.animation.Interpolator;
+
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.R;
+
+/** Coordinates the transition between Search and A-Z in All Apps. */
+public class SearchTransitionController {
+
+    // Interpolator when the user taps the QSB while already in All Apps.
+    private static final Interpolator INTERPOLATOR_WITHIN_ALL_APPS = DEACCEL_1_7;
+    // Interpolator when the user taps the QSB from home screen, so transition to all apps is
+    // happening simultaneously.
+    private static final Interpolator INTERPOLATOR_TRANSITIONING_TO_ALL_APPS = INSTANT;
+
+    /**
+     * These values represent points on the [0, 1] animation progress spectrum. They are used to
+     * animate items in the {@link SearchRecyclerView}.
+     */
+    private static final float TOP_CONTENT_FADE_PROGRESS_START = 0.133f;
+    private static final float CONTENT_FADE_PROGRESS_DURATION = 0.083f;
+    private static final float TOP_BACKGROUND_FADE_PROGRESS_START = 0.633f;
+    private static final float BACKGROUND_FADE_PROGRESS_DURATION = 0.15f;
+    private static final float CONTENT_STAGGER = 0.01f;  // Progress before next item starts fading.
+
+    private static final FloatProperty<SearchTransitionController> SEARCH_TO_AZ_PROGRESS =
+            new FloatProperty<SearchTransitionController>("searchToAzProgress") {
+                @Override
+                public Float get(SearchTransitionController controller) {
+                    return controller.getSearchToAzProgress();
+                }
+
+                @Override
+                public void setValue(SearchTransitionController controller, float progress) {
+                    controller.setSearchToAzProgress(progress);
+                }
+            };
+
+    private final ActivityAllAppsContainerView<?> mAllAppsContainerView;
+
+    private ObjectAnimator mSearchToAzAnimator = null;
+    private float mSearchToAzProgress = 1f;
+
+    public SearchTransitionController(ActivityAllAppsContainerView<?> allAppsContainerView) {
+        mAllAppsContainerView = allAppsContainerView;
+    }
+
+    /** Returns true if a transition animation is currently in progress. */
+    public boolean isRunning() {
+        return mSearchToAzAnimator != null;
+    }
+
+    /**
+     * Starts the transition to or from search state. If a transition is already in progress, the
+     * animation will start from that point with the new duration, and the previous onEndRunnable
+     * will not be called.
+     *
+     * @param goingToSearch true if will be showing search results, otherwise will be showing a-z
+     * @param duration time in ms for the animation to run
+     * @param onEndRunnable will be called when the animation finishes, unless another animation is
+     *                      scheduled in the meantime
+     */
+    public void animateToSearchState(boolean goingToSearch, long duration, Runnable onEndRunnable) {
+        float targetProgress = goingToSearch ? 0 : 1;
+
+        if (mSearchToAzAnimator != null) {
+            mSearchToAzAnimator.cancel();
+        }
+
+        mSearchToAzAnimator = ObjectAnimator.ofFloat(this, SEARCH_TO_AZ_PROGRESS, targetProgress);
+        boolean inAllApps = Launcher.getLauncher(
+                mAllAppsContainerView.getContext()).getStateManager().isInStableState(
+                LauncherState.ALL_APPS);
+        if (!inAllApps) {
+            duration = 0;  // Don't want to animate when coming from QSB.
+        }
+        mSearchToAzAnimator.setDuration(duration).setInterpolator(
+                inAllApps ? INTERPOLATOR_WITHIN_ALL_APPS : INTERPOLATOR_TRANSITIONING_TO_ALL_APPS);
+        mSearchToAzAnimator.addListener(forEndCallback(() -> mSearchToAzAnimator = null));
+        if (!goingToSearch) {
+            mSearchToAzAnimator.addListener(forSuccessCallback(() -> {
+                mAllAppsContainerView.getFloatingHeaderView().setFloatingRowsCollapsed(false);
+                mAllAppsContainerView.getFloatingHeaderView().reset(false /* animate */);
+                mAllAppsContainerView.getAppsRecyclerViewContainer().setTranslationY(0);
+            }));
+        }
+        mSearchToAzAnimator.addListener(forSuccessCallback(onEndRunnable));
+
+        mAllAppsContainerView.getFloatingHeaderView().setFloatingRowsCollapsed(true);
+        mAllAppsContainerView.getAppsRecyclerViewContainer().setVisibility(VISIBLE);
+        getSearchRecyclerView().setVisibility(VISIBLE);
+        getSearchRecyclerView().setChildAttachedConsumer(this::onSearchChildAttached);
+        mSearchToAzAnimator.start();
+    }
+
+    private SearchRecyclerView getSearchRecyclerView() {
+        return mAllAppsContainerView.getSearchRecyclerView();
+    }
+
+    private void setSearchToAzProgress(float searchToAzProgress) {
+        mSearchToAzProgress = searchToAzProgress;
+        int searchHeight = updateSearchRecyclerViewProgress();
+
+        FloatingHeaderView headerView = mAllAppsContainerView.getFloatingHeaderView();
+
+        // Add predictions + app divider height to account for predicted apps which will now be in
+        // the Search RV instead of the floating header view. Note `getFloatingRowsHeight` returns 0
+        // when predictions are not shown.
+        int appsTranslationY = searchHeight + headerView.getFloatingRowsHeight();
+
+        if (headerView.usingTabs()) {
+            // Move tabs below the search results, and fade them out in 20% of the animation.
+            headerView.setTranslationY(searchHeight);
+            headerView.setAlpha(clampToProgress(searchToAzProgress, 0.8f, 1f));
+
+            // Account for the additional padding added for the tabs.
+            appsTranslationY +=
+                    headerView.getTabsAdditionalPaddingBottom()
+                            + mAllAppsContainerView.getResources().getDimensionPixelOffset(
+                                    R.dimen.all_apps_tabs_margin_top)
+                            - headerView.getPaddingTop();
+        }
+
+        View appsContainer = mAllAppsContainerView.getAppsRecyclerViewContainer();
+        appsContainer.setTranslationY(appsTranslationY);
+        // Fade apps out with tabs (in 20% of the total animation).
+        appsContainer.setAlpha(clampToProgress(searchToAzProgress, 0.8f, 1f));
+    }
+
+    /**
+     * Updates the children views of SearchRecyclerView based on the current animation progress.
+     *
+     * @return the total height of animating views (excluding any app icons).
+     */
+    private int updateSearchRecyclerViewProgress() {
+        int numSearchResultsAnimated = 0;
+        int totalHeight = 0;
+        int appRowHeight = 0;
+        Integer top = null;
+        SearchRecyclerView searchRecyclerView = getSearchRecyclerView();
+        for (int i = 0; i < searchRecyclerView.getChildCount(); i++) {
+            View searchResultView = searchRecyclerView.getChildAt(i);
+            if (searchResultView == null) {
+                continue;
+            }
+
+            if (top == null) {
+                top = searchResultView.getTop();
+            }
+
+            if (searchResultView instanceof BubbleTextView) {
+                // The first app icon will set appRowHeight, which will also contribute to
+                // totalHeight. Additional app icons should remove the appRowHeight to remain in
+                // the same row as the first app.
+                searchResultView.setY(top + totalHeight - appRowHeight);
+                if (appRowHeight == 0) {
+                    appRowHeight = searchResultView.getHeight();
+                    totalHeight += appRowHeight;
+                }
+                // Don't scale/fade app row.
+                continue;
+            }
+
+            // Adjust content alpha based on start progress and stagger.
+            float startContentFadeProgress = Math.max(0,
+                    TOP_CONTENT_FADE_PROGRESS_START - CONTENT_STAGGER * numSearchResultsAnimated);
+            float endContentFadeProgress = Math.min(1,
+                    startContentFadeProgress + CONTENT_FADE_PROGRESS_DURATION);
+            searchResultView.setAlpha(1 - clampToProgress(mSearchToAzProgress,
+                    startContentFadeProgress, endContentFadeProgress));
+
+            // Adjust background (or decorator) alpha based on start progress and stagger.
+            float startBackgroundFadeProgress = Math.max(0,
+                    TOP_BACKGROUND_FADE_PROGRESS_START
+                            - CONTENT_STAGGER * numSearchResultsAnimated);
+            float endBackgroundFadeProgress = Math.min(1,
+                    startBackgroundFadeProgress + BACKGROUND_FADE_PROGRESS_DURATION);
+            float backgroundAlpha = 1 - clampToProgress(mSearchToAzProgress,
+                    startBackgroundFadeProgress, endBackgroundFadeProgress);
+            int adapterPosition = searchRecyclerView.getChildAdapterPosition(searchResultView);
+            boolean decoratorFilled =
+                    adapterPosition != NO_POSITION
+                            && searchRecyclerView.getApps().getAdapterItems().get(adapterPosition)
+                            .setDecorationFillAlpha((int) (255 * backgroundAlpha));
+            if (!decoratorFilled) {
+                // Try to adjust background alpha instead (e.g. for Search Edu card).
+                Drawable background = searchResultView.getBackground();
+                if (background != null) {
+                    background.setAlpha((int) (255 * backgroundAlpha));
+                }
+            }
+
+            float scaleY = 1 - mSearchToAzProgress;
+            int scaledHeight = (int) (searchResultView.getHeight() * scaleY);
+            searchResultView.setScaleY(scaleY);
+            searchResultView.setY(top + totalHeight);
+
+            numSearchResultsAnimated++;
+            totalHeight += scaledHeight;
+        }
+
+        return totalHeight - appRowHeight;
+    }
+
+    /** Called just before a child is attached to the SearchRecyclerView. */
+    private void onSearchChildAttached(View child) {
+        // Avoid allocating hardware layers for alpha changes.
+        child.forceHasOverlappingRendering(false);
+        child.setPivotY(0);
+        if (mSearchToAzProgress > 0) {
+            // Before the child is rendered, apply the animation including it to avoid flicker.
+            updateSearchRecyclerViewProgress();
+        } else {
+            // Apply default states without processing the full layout.
+            child.setAlpha(1);
+            child.setScaleY(1);
+            child.setTranslationY(0);
+            int adapterPosition = getSearchRecyclerView().getChildAdapterPosition(child);
+            if (adapterPosition != NO_POSITION) {
+                getSearchRecyclerView().getApps().getAdapterItems().get(adapterPosition)
+                        .setDecorationFillAlpha(255);
+            }
+            if (child.getBackground() != null) {
+                child.getBackground().setAlpha(255);
+            }
+        }
+    }
+
+    private float getSearchToAzProgress() {
+        return mSearchToAzProgress;
+    }
+}
diff --git a/src/com/android/launcher3/allapps/WorkAdapterProvider.java b/src/com/android/launcher3/allapps/WorkAdapterProvider.java
deleted file mode 100644
index 76d08c8..0000000
--- a/src/com/android/launcher3/allapps/WorkAdapterProvider.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.allapps;
-
-import android.content.SharedPreferences;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import com.android.launcher3.R;
-import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
-import com.android.launcher3.model.StringCache;
-import com.android.launcher3.views.ActivityContext;
-
-import java.util.ArrayList;
-
-/**
- * A UI expansion wrapper providing for providing work profile specific views
- */
-public class WorkAdapterProvider extends BaseAdapterProvider {
-
-    public static final String KEY_WORK_EDU_STEP = "showed_work_profile_edu";
-
-    private static final int VIEW_TYPE_WORK_EDU_CARD = 1 << 20;
-    private static final int VIEW_TYPE_WORK_DISABLED_CARD = 1 << 21;
-
-    @WorkProfileManager.WorkProfileState
-    private int mState;
-    private ActivityContext mActivityContext;
-    private SharedPreferences mPreferences;
-
-    WorkAdapterProvider(ActivityContext activityContext, SharedPreferences prefs) {
-        mActivityContext = activityContext;
-        mPreferences = prefs;
-    }
-
-    @Override
-    public void onBindView(AllAppsGridAdapter.ViewHolder holder, int position) {
-        if (holder.itemView instanceof WorkEduCard) {
-            ((WorkEduCard) holder.itemView).setPosition(position);
-        }
-    }
-
-    @Override
-    public AllAppsGridAdapter.ViewHolder onCreateViewHolder(LayoutInflater layoutInflater,
-            ViewGroup parent, int viewType) {
-        int viewId = viewType == VIEW_TYPE_WORK_DISABLED_CARD ? R.layout.work_apps_paused
-                : R.layout.work_apps_edu;
-        View view = layoutInflater.inflate(viewId, parent, false);
-        setDeviceManagementResources(view, viewType);
-        return new AllAppsGridAdapter.ViewHolder(view);
-    }
-
-    private void setDeviceManagementResources(View view, int viewType) {
-        StringCache cache = mActivityContext.getStringCache();
-        if (cache == null) {
-            return;
-        }
-        if (viewType == VIEW_TYPE_WORK_DISABLED_CARD) {
-            setWorkProfilePausedResources(view, cache);
-        } else {
-            setWorkProfileEduResources(view, cache);
-        }
-    }
-
-    private void setWorkProfilePausedResources(View view, StringCache cache) {
-        TextView title = view.findViewById(R.id.work_apps_paused_title);
-        title.setText(cache.workProfilePausedTitle);
-
-        TextView body = view.findViewById(R.id.work_apps_paused_content);
-        body.setText(cache.workProfilePausedDescription);
-
-        TextView button = view.findViewById(R.id.enable_work_apps);
-        button.setText(cache.workProfileEnableButton);
-    }
-
-    private void setWorkProfileEduResources(View view, StringCache cache) {
-        TextView title = view.findViewById(R.id.work_apps_paused_title);
-        title.setText(cache.workProfileEdu);
-
-    }
-
-    /**
-     * returns whether or not work apps should be visible in work tab.
-     */
-    public boolean shouldShowWorkApps() {
-        return mState != WorkProfileManager.STATE_DISABLED;
-    }
-
-    /**
-     * Adds work profile specific adapter items to adapterItems and returns number of items added
-     */
-    public int addWorkItems(ArrayList<AllAppsGridAdapter.AdapterItem> adapterItems) {
-        if (mState == WorkProfileManager.STATE_DISABLED) {
-            //add disabled card here.
-            adapterItems.add(new AdapterItem(VIEW_TYPE_WORK_DISABLED_CARD));
-        } else if (mState == WorkProfileManager.STATE_ENABLED && !isEduSeen()) {
-            adapterItems.add(new AdapterItem(VIEW_TYPE_WORK_EDU_CARD));
-        }
-
-        return adapterItems.size();
-    }
-
-    /**
-     * Sets the current state of work profile
-     */
-    public void updateCurrentState(@WorkProfileManager.WorkProfileState int state) {
-        mState = state;
-    }
-
-    @Override
-    public boolean isViewSupported(int viewType) {
-        return viewType == VIEW_TYPE_WORK_DISABLED_CARD || viewType == VIEW_TYPE_WORK_EDU_CARD;
-    }
-
-    @Override
-    public int getItemsPerRow(int viewType, int appsPerRow) {
-        return 1;
-    }
-
-    private boolean isEduSeen() {
-        return mPreferences.getInt(KEY_WORK_EDU_STEP, 0) != 0;
-    }
-}
diff --git a/src/com/android/launcher3/allapps/WorkEduCard.java b/src/com/android/launcher3/allapps/WorkEduCard.java
index 539cff1..968a556 100644
--- a/src/com/android/launcher3/allapps/WorkEduCard.java
+++ b/src/com/android/launcher3/allapps/WorkEduCard.java
@@ -24,9 +24,11 @@
 import android.view.animation.Animation;
 import android.view.animation.AnimationUtils;
 import android.widget.FrameLayout;
+import android.widget.TextView;
 
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.model.StringCache;
 import com.android.launcher3.views.ActivityContext;
 
 /**
@@ -72,12 +74,18 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         findViewById(R.id.action_btn).setOnClickListener(this);
+
+        StringCache cache = mActivityContext.getStringCache();
+        if (cache != null) {
+            TextView title = findViewById(R.id.work_apps_paused_title);
+            title.setText(cache.workProfileEdu);
+        }
     }
 
     @Override
     public void onClick(View view) {
         startAnimation(mDismissAnim);
-        Utilities.getPrefs(getContext()).edit().putInt(WorkAdapterProvider.KEY_WORK_EDU_STEP,
+        Utilities.getPrefs(getContext()).edit().putInt(WorkProfileManager.KEY_WORK_EDU_STEP,
                 1).apply();
     }
 
diff --git a/src/com/android/launcher3/allapps/WorkModeSwitch.java b/src/com/android/launcher3/allapps/WorkModeSwitch.java
index edb0128..15fb77c 100644
--- a/src/com/android/launcher3/allapps/WorkModeSwitch.java
+++ b/src/com/android/launcher3/allapps/WorkModeSwitch.java
@@ -91,19 +91,16 @@
         MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
         if (lp != null) {
             int bottomMargin = getResources().getDimensionPixelSize(R.dimen.work_fab_margin_bottom);
+            DeviceProfile dp = ActivityContext.lookupContext(getContext()).getDeviceProfile();
             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);
+                bottomMargin += dp.hotseatQsbHeight;
             }
 
-            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;
-                }
+            if (!dp.isGestureMode && dp.isTaskbarPresent) {
+                bottomMargin += dp.taskbarSize;
+            } else {
+                bottomMargin += insets.bottom;
             }
 
             lp.bottomMargin = bottomMargin;
@@ -171,12 +168,14 @@
 
     @Override
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
-        if (Utilities.ATLEAST_R && isEnabled()) {
+        if (!Utilities.ATLEAST_R) {
+            return insets;
+        }
+        if (insets.isVisible(WindowInsets.Type.ime())) {
+            Insets keyboardInsets = insets.getInsets(WindowInsets.Type.ime());
+            setTranslationY(mInsets.bottom - keyboardInsets.bottom);
+        } else {
             setTranslationY(0);
-            if (insets.isVisible(WindowInsets.Type.ime())) {
-                Insets keyboardInsets = insets.getInsets(WindowInsets.Type.ime());
-                setTranslationY(mInsets.bottom - keyboardInsets.bottom);
-            }
         }
         return insets;
     }
diff --git a/src/com/android/launcher3/allapps/WorkPausedCard.java b/src/com/android/launcher3/allapps/WorkPausedCard.java
index 729622f..26a7803 100644
--- a/src/com/android/launcher3/allapps/WorkPausedCard.java
+++ b/src/com/android/launcher3/allapps/WorkPausedCard.java
@@ -23,9 +23,11 @@
 import android.view.View;
 import android.widget.Button;
 import android.widget.LinearLayout;
+import android.widget.TextView;
 
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.model.StringCache;
 import com.android.launcher3.views.ActivityContext;
 
 /**
@@ -49,12 +51,27 @@
         mActivityContext = ActivityContext.lookupContext(getContext());
     }
 
-
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
         mBtn = findViewById(R.id.enable_work_apps);
         mBtn.setOnClickListener(this);
+
+        StringCache cache = mActivityContext.getStringCache();
+        if (cache != null) {
+            setWorkProfilePausedResources(cache);
+        }
+    }
+
+    private void setWorkProfilePausedResources(StringCache cache) {
+        TextView title = findViewById(R.id.work_apps_paused_title);
+        title.setText(cache.workProfilePausedTitle);
+
+        TextView body = findViewById(R.id.work_apps_paused_content);
+        body.setText(cache.workProfilePausedDescription);
+
+        TextView button = findViewById(R.id.enable_work_apps);
+        button.setText(cache.workProfileEnableButton);
     }
 
     @Override
diff --git a/src/com/android/launcher3/allapps/WorkProfileManager.java b/src/com/android/launcher3/allapps/WorkProfileManager.java
index 2f5b7a2..cfac985 100644
--- a/src/com/android/launcher3/allapps/WorkProfileManager.java
+++ b/src/com/android/launcher3/allapps/WorkProfileManager.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.allapps;
 
+import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_WORK_DISABLED_CARD;
+import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_WORK_EDU_CARD;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
@@ -31,13 +33,14 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 
-import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
+import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
 import java.util.function.Predicate;
 
 /**
@@ -48,13 +51,12 @@
 public class WorkProfileManager implements PersonalWorkSlidingTabStrip.OnActivePageChangedListener {
     private static final String TAG = "WorkProfileManager";
 
+    public static final String KEY_WORK_EDU_STEP = "showed_work_profile_edu";
 
     public static final int STATE_ENABLED = 1;
     public static final int STATE_DISABLED = 2;
     public static final int STATE_TRANSITION = 3;
 
-    private final UserManager mUserManager;
-
     /**
      * Work profile manager states
      */
@@ -64,26 +66,23 @@
             STATE_TRANSITION
     })
     @Retention(RetentionPolicy.SOURCE)
-    public @interface WorkProfileState {
-    }
+    public @interface WorkProfileState { }
 
+    private final UserManager mUserManager;
     private final BaseAllAppsContainerView<?> mAllApps;
-    private final WorkAdapterProvider mAdapterProvider;
     private final Predicate<ItemInfo> mMatcher;
 
     private WorkModeSwitch mWorkModeSwitch;
-    private final DeviceProfile mDeviceProfile;
 
     @WorkProfileState
     private int mCurrentState;
+    private SharedPreferences mPreferences;
 
-
-    public WorkProfileManager(UserManager userManager, BaseAllAppsContainerView<?> allApps,
-            SharedPreferences preferences, DeviceProfile deviceProfile) {
+    public WorkProfileManager(
+            UserManager userManager, BaseAllAppsContainerView<?> allApps, SharedPreferences prefs) {
         mUserManager = userManager;
         mAllApps = allApps;
-        mDeviceProfile = deviceProfile;
-        mAdapterProvider = new WorkAdapterProvider(allApps.mActivityContext, preferences);
+        mPreferences = prefs;
         mMatcher = mAllApps.mPersonalMatcher.negate();
     }
 
@@ -120,7 +119,6 @@
 
     private void updateCurrentState(@WorkProfileState int currentState) {
         mCurrentState = currentState;
-        mAdapterProvider.updateCurrentState(currentState);
         if (getAH() != null) {
             getAH().mAppsList.updateAdapterItems();
         }
@@ -161,10 +159,6 @@
         mWorkModeSwitch = null;
     }
 
-    public WorkAdapterProvider getAdapterProvider() {
-        return mAdapterProvider;
-    }
-
     public Predicate<ItemInfo> getMatcher() {
         return mMatcher;
     }
@@ -181,4 +175,28 @@
     public int getCurrentState() {
         return mCurrentState;
     }
+
+    /**
+     * returns whether or not work apps should be visible in work tab.
+     */
+    public boolean shouldShowWorkApps() {
+        return mCurrentState != WorkProfileManager.STATE_DISABLED;
+    }
+
+    /**
+     * Adds work profile specific adapter items to adapterItems and returns number of items added
+     */
+    public int addWorkItems(ArrayList<AdapterItem> adapterItems) {
+        if (mCurrentState == WorkProfileManager.STATE_DISABLED) {
+            //add disabled card here.
+            adapterItems.add(new AdapterItem(VIEW_TYPE_WORK_DISABLED_CARD));
+        } else if (mCurrentState == WorkProfileManager.STATE_ENABLED && !isEduSeen()) {
+            adapterItems.add(new AdapterItem(VIEW_TYPE_WORK_EDU_CARD));
+        }
+        return adapterItems.size();
+    }
+
+    private boolean isEduSeen() {
+        return mPreferences.getInt(KEY_WORK_EDU_STEP, 0) != 0;
+    }
 }
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
index 6539c05..78c305b 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
@@ -131,7 +131,7 @@
     public void initializeSearch(ActivityAllAppsContainerView<?> appsView) {
         mAppsView = appsView;
         mSearchBarController.initialize(
-                new DefaultAppSearchAlgorithm(getContext()),
+                new DefaultAppSearchAlgorithm(getContext(), true),
                 this, mLauncher, this);
     }
 
@@ -168,14 +168,11 @@
     public void onSearchResult(String query, ArrayList<AdapterItem> items) {
         if (items != null) {
             mAppsView.setSearchResults(items);
-            mAppsView.setLastSearchQuery(query);
         }
     }
 
     @Override
     public void clearSearchResult() {
-        mAppsView.setSearchResults(null);
-
         // Clear the search query
         mSearchQueryBuilder.clear();
         mSearchQueryBuilder.clearSpans();
diff --git a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
index 4eceb71..ab47097 100644
--- a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
+++ b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
@@ -15,12 +15,14 @@
  */
 package com.android.launcher3.allapps.search;
 
+import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_EMPTY_SEARCH;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
 import android.content.Context;
 import android.os.Handler;
 
 import androidx.annotation.AnyThread;
+import androidx.annotation.NonNull;
 
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
@@ -44,10 +46,16 @@
 
     private final LauncherAppState mAppState;
     private final Handler mResultHandler;
+    private final boolean mAddNoResultsMessage;
 
     public DefaultAppSearchAlgorithm(Context context) {
+        this(context, false);
+    }
+
+    public DefaultAppSearchAlgorithm(Context context, boolean addNoResultsMessage) {
         mAppState = LauncherAppState.getInstance(context);
         mResultHandler = new Handler(MAIN_EXECUTOR.getLooper());
+        mAddNoResultsMessage = addNoResultsMessage;
     }
 
     @Override
@@ -61,13 +69,26 @@
     public void doSearch(String query, SearchCallback<AdapterItem> callback) {
         mAppState.getModel().enqueueModelUpdateTask(new BaseModelUpdateTask() {
             @Override
-            public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+            public void execute(@NonNull final LauncherAppState app,
+                    @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) {
                 ArrayList<AdapterItem> result = getTitleMatchResult(apps.data, query);
+                if (mAddNoResultsMessage && result.isEmpty()) {
+                    result.add(getEmptyMessageAdapterItem(query));
+                }
                 mResultHandler.post(() -> callback.onSearchResult(query, result));
             }
         });
     }
 
+    private static AdapterItem getEmptyMessageAdapterItem(String query) {
+        AdapterItem item = new AdapterItem(VIEW_TYPE_EMPTY_SEARCH);
+        // Add a place holder info to propagate the query
+        AppInfo placeHolder = new AppInfo();
+        placeHolder.title = query;
+        item.itemInfo = placeHolder;
+        return item;
+    }
+
     /**
      * Filters {@link AppInfo}s matching specified query
      */
diff --git a/src/com/android/launcher3/allapps/search/DefaultSearchAdapterProvider.java b/src/com/android/launcher3/allapps/search/DefaultSearchAdapterProvider.java
index a95bd51..4fb732d 100644
--- a/src/com/android/launcher3/allapps/search/DefaultSearchAdapterProvider.java
+++ b/src/com/android/launcher3/allapps/search/DefaultSearchAdapterProvider.java
@@ -84,4 +84,9 @@
     public RecyclerView.ItemDecoration getDecorator() {
         return mDecoration;
     }
+
+    @Override
+    public void clearHighlightedItem() {
+        mHighlightedView = null;
+    }
 }
diff --git a/src/com/android/launcher3/allapps/search/SearchAdapterProvider.java b/src/com/android/launcher3/allapps/search/SearchAdapterProvider.java
index bc52784..3890741 100644
--- a/src/com/android/launcher3/allapps/search/SearchAdapterProvider.java
+++ b/src/com/android/launcher3/allapps/search/SearchAdapterProvider.java
@@ -58,4 +58,9 @@
      * Returns the item decorator.
      */
     public abstract RecyclerView.ItemDecoration getDecorator();
+
+    /**
+     * Clear the highlighted view.
+     */
+    public abstract void clearHighlightedItem();
 }
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/AnimationSuccessListener.java b/src/com/android/launcher3/anim/AnimationSuccessListener.java
index a312070..6196df2 100644
--- a/src/com/android/launcher3/anim/AnimationSuccessListener.java
+++ b/src/com/android/launcher3/anim/AnimationSuccessListener.java
@@ -19,6 +19,8 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 
+import androidx.annotation.CallSuper;
+
 /**
  * Extension of {@link AnimatorListenerAdapter} for listening for non-cancelled animations
  */
@@ -27,6 +29,7 @@
     protected boolean mCancelled = false;
 
     @Override
+    @CallSuper
     public void onAnimationCancel(Animator animation) {
         mCancelled = true;
     }
diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java
index 0a77aa7..b55a1e4 100644
--- a/src/com/android/launcher3/anim/Interpolators.java
+++ b/src/com/android/launcher3/anim/Interpolators.java
@@ -57,6 +57,10 @@
     public static final Interpolator DECELERATED_EASE = new PathInterpolator(0, 0, .2f, 1f);
     public static final Interpolator ACCELERATED_EASE = new PathInterpolator(0.4f, 0, 1f, 1f);
 
+    /**
+     * The default emphasized interpolator. Used for hero / emphasized movement of content.
+     */
+    public static final Interpolator EMPHASIZED = createEmphasizedInterpolator();
     public static final Interpolator EMPHASIZED_ACCELERATE = new PathInterpolator(
             0.3f, 0f, 0.8f, 0.15f);
     public static final Interpolator EMPHASIZED_DECELERATE = new PathInterpolator(
@@ -79,6 +83,7 @@
         EXAGGERATED_EASE = new PathInterpolator(exaggeratedEase);
     }
 
+    public static final Interpolator OVERSHOOT_0_75 = new OvershootInterpolator(0.75f);
     public static final Interpolator OVERSHOOT_1_2 = new OvershootInterpolator(1.2f);
     public static final Interpolator OVERSHOOT_1_7 = new OvershootInterpolator(1.7f);
 
@@ -87,7 +92,6 @@
     public static final Interpolator TOUCH_RESPONSE_INTERPOLATOR_ACCEL_DEACCEL =
             v -> ACCEL_DEACCEL.getInterpolation(TOUCH_RESPONSE_INTERPOLATOR.getInterpolation(v));
 
-
     /**
      * Inversion of ZOOM_OUT, compounded with an ease-out.
      */
@@ -218,4 +222,14 @@
     public static Interpolator reverse(Interpolator interpolator) {
         return t -> 1 - interpolator.getInterpolation(1 - t);
     }
+
+    // Create the default emphasized interpolator
+    private static PathInterpolator createEmphasizedInterpolator() {
+        Path path = new Path();
+        // Doing the same as fast_out_extra_slow_in
+        path.moveTo(0f, 0f);
+        path.cubicTo(0.05f, 0f, 0.133333f, 0.06f, 0.166666f, 0.4f);
+        path.cubicTo(0.208333f, 0.82f, 0.25f, 1f, 1f, 1f);
+        return new PathInterpolator(path);
+    }
 }
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/compat/AccessibilityManagerCompat.java b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
index dd58123..770e931 100644
--- a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
+++ b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
@@ -27,7 +27,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.Utilities;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 
 public class AccessibilityManagerCompat {
 
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 33beacd..4a47e98 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -63,15 +63,20 @@
      * Declare a new ToggleableFlag below. Give it a unique key (e.g. "QSB_ON_FIRST_SCREEN"),
      *    and set a default value for the flag. This will be the default value on Debug builds.
      */
+    public static final BooleanFlag ENABLE_INPUT_CONSUMER_REASON_LOGGING = getDebugFlag(
+            "ENABLE_INPUT_CONSUMER_REASON_LOGGING",
+            true,
+            "Log the reason why an Input Consumer was selected for a gesture.");
+
+    public static final BooleanFlag ENABLE_GESTURE_ERROR_DETECTION = getDebugFlag(
+            "ENABLE_GESTURE_ERROR_DETECTION",
+            true,
+            "Analyze gesture events and log detected errors");
+
     // When enabled the promise icon is visible in all apps while installation an app.
     public static final BooleanFlag PROMISE_APPS_IN_ALL_APPS = getDebugFlag(
             "PROMISE_APPS_IN_ALL_APPS", false, "Add promise icon in all-apps");
 
-    // When enabled a promise icon is added to the home screen when install session is active.
-    public static final BooleanFlag PROMISE_APPS_NEW_INSTALLS = getDebugFlag(
-            "PROMISE_APPS_NEW_INSTALLS", true,
-            "Adds a promise icon to the home screen for new install sessions.");
-
     // TODO: b/206508141: Long pressing on some icons on home screen cause launcher to crash.
     public static final BooleanFlag ENABLE_LOCAL_COLOR_POPUPS = getDebugFlag(
             "ENABLE_LOCAL_COLOR_POPUPS", false, "Enable local color extraction for popups.");
@@ -82,10 +87,6 @@
     public static final BooleanFlag ENABLE_QUICKSTEP_LIVE_TILE = getDebugFlag(
             "ENABLE_QUICKSTEP_LIVE_TILE", true, "Enable live tile in Quickstep overview");
 
-    public static final BooleanFlag ENABLE_QUICKSTEP_WIDGET_APP_START = getDebugFlag(
-            "ENABLE_QUICKSTEP_WIDGET_APP_START", true,
-            "Enable Quickstep animation when launching activities from an app widget");
-
     public static final BooleanFlag ENABLE_DEVICE_SEARCH = new DeviceFlag(
             "ENABLE_DEVICE_SEARCH", true, "Allows on device search in all apps");
 
@@ -93,8 +94,8 @@
             getDebugFlag("ENABLE_FLOATING_SEARCH_BAR", false,
                     "Keep All Apps search bar at the bottom (but above keyboard if open)");
 
-    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");
@@ -113,30 +114,14 @@
             "ENABLE_PEOPLE_TILE_PREVIEW", false,
             "Experimental: Shows conversation shortcuts on home screen as search results");
 
-    public static final BooleanFlag FOLDER_NAME_SUGGEST = new DeviceFlag(
-            "FOLDER_NAME_SUGGEST", true,
-            "Suggests folder names instead of blank text.");
-
     public static final BooleanFlag FOLDER_NAME_MAJORITY_RANKING = getDebugFlag(
             "FOLDER_NAME_MAJORITY_RANKING", true,
             "Suggests folder names based on majority based ranking.");
 
-    public static final BooleanFlag ENABLE_PREDICTION_DISMISS = getDebugFlag(
-            "ENABLE_PREDICTION_DISMISS", true, "Allow option to dimiss apps from predicted list");
-
     public static final BooleanFlag ASSISTANT_GIVES_LAUNCHER_FOCUS = getDebugFlag(
             "ASSISTANT_GIVES_LAUNCHER_FOCUS", false,
             "Allow Launcher to handle nav bar gestures while Assistant is running over it");
 
-    public static final BooleanFlag HOTSEAT_MIGRATE_TO_FOLDER = getDebugFlag(
-            "HOTSEAT_MIGRATE_TO_FOLDER", false, "Should move hotseat items into a folder");
-
-    public static final BooleanFlag ENABLE_DEEP_SHORTCUT_ICON_CACHE = getDebugFlag(
-            "ENABLE_DEEP_SHORTCUT_ICON_CACHE", true, "R/W deep shortcut in IconCache");
-
-    public static final BooleanFlag ENABLE_THEMED_ICONS = getDebugFlag(
-            "ENABLE_THEMED_ICONS", true, "Enable themed icons on workspace");
-
     public static final BooleanFlag ENABLE_BULK_WORKSPACE_ICON_LOADING = getDebugFlag(
             "ENABLE_BULK_WORKSPACE_ICON_LOADING",
             true,
@@ -166,15 +151,18 @@
             "ENABLE_SMARTSPACE_DISMISS", true,
             "Adds a menu option to dismiss the current Enhanced Smartspace card.");
 
+    /**
+     * Enables region sampling for text color: Needs system health assessment before turning on
+     */
+    public static final BooleanFlag ENABLE_REGION_SAMPLING =  getDebugFlag(
+            "ENABLE_REGION_SAMPLING", false,
+            "Enable region sampling to determine color of text on screen.");
+
     public static final BooleanFlag ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS =
             getDebugFlag(
             "ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS", false,
             "Always use hardware optimization for folder animations.");
 
-    public static final BooleanFlag ENABLE_ALL_APPS_EDU = getDebugFlag(
-            "ENABLE_ALL_APPS_EDU", true,
-            "Shows user a tutorial on how to get to All Apps after X amount of attempts.");
-
     public static final BooleanFlag SEPARATE_RECENTS_ACTIVITY = getDebugFlag(
             "SEPARATE_RECENTS_ACTIVITY", false,
             "Uses a separate recents activity instead of using the integrated recents+Launcher UI");
@@ -183,10 +171,6 @@
             "ENABLE_MINIMAL_DEVICE", false,
             "Allow user to toggle minimal device mode in launcher.");
 
-    public static final BooleanFlag EXPANDED_SMARTSPACE = new DeviceFlag(
-            "EXPANDED_SMARTSPACE", false, "Expands smartspace height to two rows. "
-              + "Any apps occupying the first row will be removed from workspace.");
-
     // TODO: b/172467144 Remove ENABLE_LAUNCHER_ACTIVITY_THEME_CROSSFADE feature flag.
     public static final BooleanFlag ENABLE_LAUNCHER_ACTIVITY_THEME_CROSSFADE = new DeviceFlag(
             "ENABLE_LAUNCHER_ACTIVITY_THEME_CROSSFADE", false, "Enables a "
@@ -209,9 +193,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");
 
@@ -265,6 +246,11 @@
     public static final BooleanFlag ENABLE_ONE_SEARCH_MOTION = new DeviceFlag(
             "ENABLE_ONE_SEARCH_MOTION", true, "Enables animations in OneSearch.");
 
+    public static final BooleanFlag ENABLE_KEYBOARD_TRANSITION_SYNC = new DeviceFlag(
+            "ENABLE_KEYBOARD_TRANSITION_SYNC", IS_STUDIO_BUILD,
+            "Enable option to synchronize the keyboard open and close animations when transitioning"
+                    + " between home and all apps");
+
     public static final BooleanFlag ENABLE_SHOW_KEYBOARD_OPTION_IN_ALL_APPS = new DeviceFlag(
             "ENABLE_SHOW_KEYBOARD_OPTION_IN_ALL_APPS", true,
             "Enable option to show keyboard when going to all-apps");
@@ -277,6 +263,32 @@
             "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 final BooleanFlag CONTINUOUS_VIEW_TREE_CAPTURE = getDebugFlag(
+            "CONTINUOUS_VIEW_TREE_CAPTURE", false, "Capture View tree every frame");
+
+    public static final BooleanFlag FOLDABLE_WORKSPACE_REORDER = getDebugFlag(
+            "FOLDABLE_WORKSPACE_REORDER", true,
+            "In foldables, when reordering the icons and widgets, is now going to use both sides");
+
+    public static final BooleanFlag ENABLE_WIDGET_PICKER_DEPTH = new DeviceFlag(
+            "ENABLE_WIDGET_PICKER_DEPTH", false, "Enable changing depth in widget picker.");
+
+    public static final BooleanFlag SHOW_DELIGHTFUL_PAGINATION_FOLDER = new DeviceFlag(
+            "SHOW_DELIGHTFUL_PAGINATION_FOLDER", false,
+            "Enable showing the new 'delightful pagination'"
+                    + " which is a brand new animation for folder pagination");
+
+    public static final BooleanFlag POPUP_MATERIAL_U = new DeviceFlag(
+            "POPUP_MATERIAL_U", false, "Switch popup UX to use material U");
+
     public static void initialize(Context context) {
         synchronized (sDebugFlags) {
             for (DebugFlag flag : sDebugFlags) {
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index 466b268..05b1984 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -31,6 +31,7 @@
 import android.content.ClipData;
 import android.content.ClipDescription;
 import android.content.Intent;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.LauncherApps.PinItemRequest;
 import android.content.pm.ShortcutInfo;
 import android.content.res.Configuration;
@@ -62,6 +63,8 @@
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.pm.PinRequestHelper;
+import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.views.AbstractSlideInView;
 import com.android.launcher3.views.BaseDragLayer;
@@ -136,13 +139,25 @@
         mAccessibilityManager =
                 getApplicationContext().getSystemService(AccessibilityManager.class);
 
-        if (mRequest.getRequestType() == PinItemRequest.REQUEST_TYPE_SHORTCUT) {
-            setupShortcut();
-        } else {
-            if (!setupWidget()) {
-                // TODO: show error toast?
-                finish();
-            }
+        PackageUserKey targetApp = null;
+        switch (mRequest.getRequestType()) {
+            case PinItemRequest.REQUEST_TYPE_SHORTCUT:
+                targetApp = setupShortcut();
+                break;
+            case PinItemRequest.REQUEST_TYPE_APPWIDGET:
+                targetApp = setupWidget();
+                break;
+        }
+        if (targetApp == null) {
+            // TODO: show error toast?
+            finish();
+            return;
+        }
+        ApplicationInfo info = new PackageManagerHelper(this)
+                .getApplicationInfo(targetApp.mPackageName, targetApp.mUser, 0);
+        if (info == null) {
+            finish();
+            return;
         }
 
         WidgetCellPreview previewContainer = mWidgetCell.findViewById(
@@ -156,8 +171,10 @@
             logCommand(LAUNCHER_ADD_EXTERNAL_ITEM_START);
         }
 
+        // Set the label synchronously instead of via IconCache as this is the first thing
+        // user sees
         TextView widgetAppName = findViewById(R.id.widget_appName);
-        widgetAppName.setText(getApplicationInfo().labelRes);
+        widgetAppName.setText(info.loadLabel(getPackageManager()));
 
         mSlideInView = findViewById(R.id.add_item_bottom_sheet);
         mSlideInView.addOnCloseListener(this);
@@ -246,20 +263,23 @@
         }
     }
 
-    private void setupShortcut() {
+    private PackageUserKey setupShortcut() {
         PinShortcutRequestActivityInfo shortcutInfo =
                 new PinShortcutRequestActivityInfo(mRequest, this);
         mWidgetCell.getWidgetView().setTag(new PendingAddShortcutInfo(shortcutInfo));
         applyWidgetItemAsync(
                 () -> new WidgetItem(shortcutInfo, mApp.getIconCache(), getPackageManager()));
+        return new PackageUserKey(
+                mRequest.getShortcutInfo().getPackage(),
+                mRequest.getShortcutInfo().getUserHandle());
     }
 
-    private boolean setupWidget() {
+    private PackageUserKey setupWidget() {
         LauncherAppWidgetProviderInfo widgetInfo = LauncherAppWidgetProviderInfo
                 .fromProviderInfo(this, mRequest.getAppWidgetProviderInfo(this));
         if (widgetInfo.minSpanX > mIdp.numColumns || widgetInfo.minSpanY > mIdp.numRows) {
             // Cannot add widget
-            return false;
+            return null;
         }
         mWidgetCell.setRemoteViewsPreview(PinItemDragListener.getPreview(mRequest));
 
@@ -274,7 +294,7 @@
         mWidgetCell.getWidgetView().setTag(pendingInfo);
 
         applyWidgetItemAsync(() -> new WidgetItem(widgetInfo, mIdp, mApp.getIconCache()));
-        return true;
+        return new PackageUserKey(widgetInfo.provider.getPackageName(), widgetInfo.getUser());
     }
 
     private void applyWidgetItemAsync(final Supplier<WidgetItem> itemProvider) {
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 35cdfef..8616f35 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -34,7 +34,7 @@
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.views.ActivityContext;
 
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/dragndrop/LauncherDragController.java b/src/com/android/launcher3/dragndrop/LauncherDragController.java
index dcbfa50..75f4ad6 100644
--- a/src/com/android/launcher3/dragndrop/LauncherDragController.java
+++ b/src/com/android/launcher3/dragndrop/LauncherDragController.java
@@ -37,7 +37,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.accessibility.DragViewStateAnnouncer;
 import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 
 /**
  * Drag controller for Launcher activity
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index e68ebdb..7e14912 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -368,9 +368,7 @@
 
     public void startEditingFolderName() {
         post(() -> {
-            if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
-                showLabelSuggestions();
-            }
+            showLabelSuggestions();
             mFolderName.setHint("");
             mIsEditingName = true;
         });
@@ -1080,8 +1078,7 @@
         if (!items.isEmpty()) {
             mLauncherDelegate.getModelWriter().moveItemsInDatabase(items, mInfo.id, 0);
         }
-        if (FeatureFlags.FOLDER_NAME_SUGGEST.get() && !isBind
-                && total > 1 /* no need to update if there's one icon */) {
+        if (!isBind && total > 1 /* no need to update if there's one icon */) {
             Executors.MODEL_EXECUTOR.post(() -> {
                 FolderNameInfos nameInfos = new FolderNameInfos();
                 FolderNameProvider fnp = FolderNameProvider.newInstance(getContext());
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index b1e2701..33e0902 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -57,7 +57,6 @@
 import com.android.launcher3.Workspace;
 import com.android.launcher3.allapps.ActivityAllAppsContainerView;
 import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dot.FolderDotInfo;
 import com.android.launcher3.dragndrop.BaseItemDragListener;
 import com.android.launcher3.dragndrop.DragLayer;
@@ -418,35 +417,23 @@
             if (!itemAdded) mPreviewItemManager.hidePreviewItem(index, true);
 
             FolderNameInfos nameInfos = new FolderNameInfos();
-            if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
-                Executors.MODEL_EXECUTOR.post(() -> {
-                    d.folderNameProvider.getSuggestedFolderName(
-                            getContext(), mInfo.contents, nameInfos);
-                    showFinalView(finalIndex, item, nameInfos, d.logInstanceId);
-                });
-            } else {
-                showFinalView(finalIndex, item, nameInfos, d.logInstanceId);
-            }
+            Executors.MODEL_EXECUTOR.post(() -> {
+                d.folderNameProvider.getSuggestedFolderName(
+                        getContext(), mInfo.contents, nameInfos);
+                postDelayed(() -> {
+                    setLabelSuggestion(nameInfos, d.logInstanceId);
+                    invalidate();
+                }, DROP_IN_ANIMATION_DURATION);
+            });
         } else {
             addItem(item);
         }
     }
 
-    private void showFinalView(int finalIndex, final WorkspaceItemInfo item,
-            FolderNameInfos nameInfos, InstanceId instanceId) {
-        postDelayed(() -> {
-            setLabelSuggestion(nameInfos, instanceId);
-            invalidate();
-        }, DROP_IN_ANIMATION_DURATION);
-    }
-
     /**
      * Set the suggested folder name.
      */
     public void setLabelSuggestion(FolderNameInfos nameInfos, InstanceId instanceId) {
-        if (!FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
-            return;
-        }
         if (!mInfo.getLabelState().equals(LabelState.UNLABELED)) {
             return;
         }
diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java
index 5021644..bf59594 100644
--- a/src/com/android/launcher3/folder/FolderNameProvider.java
+++ b/src/com/android/launcher3/folder/FolderNameProvider.java
@@ -24,6 +24,7 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.WorkerThread;
 
 import com.android.launcher3.LauncherAppState;
@@ -192,7 +193,8 @@
 
     private class FolderNameWorker extends BaseModelUpdateTask {
         @Override
-        public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+        public void execute(@NonNull final LauncherAppState app,
+                @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) {
             mFolderInfos = dataModel.folders.clone();
             mAppInfos = Arrays.asList(apps.copyData());
         }
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/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index f027b33..7457f30 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -16,24 +16,16 @@
 
 package com.android.launcher3.graphics;
 
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-
 import android.content.Context;
 import android.graphics.Bitmap;
-import android.graphics.BlurMaskFilter;
 import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.view.View;
 
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.R;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.icons.BitmapRenderer;
 import com.android.launcher3.icons.FastBitmapDrawable;
@@ -41,8 +33,6 @@
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
 
-import java.nio.ByteBuffer;
-
 /**
  * A utility class to generate preview bitmap for dragging.
  */
@@ -57,9 +47,6 @@
 
     public final int blurSizeOutline;
 
-    private OutlineGeneratorCallback mOutlineGeneratorCallback;
-    public Bitmap generatedDragOutline;
-
     public DragPreviewProvider(View view) {
         this(view, view.getContext());
     }
@@ -129,15 +116,6 @@
         return null;
     }
 
-    public final void generateDragOutline(Bitmap preview) {
-        if (FeatureFlags.IS_STUDIO_BUILD && mOutlineGeneratorCallback != null) {
-            throw new RuntimeException("Drag outline generated twice");
-        }
-
-        mOutlineGeneratorCallback = new OutlineGeneratorCallback(preview);
-        UI_HELPER_EXECUTOR.post(mOutlineGeneratorCallback);
-    }
-
     protected static Rect getDrawableBounds(Drawable d) {
         Rect bounds = new Rect();
         d.copyBounds(bounds);
@@ -184,92 +162,4 @@
     protected Bitmap convertPreviewToAlphaBitmap(Bitmap preview) {
         return preview.copy(Bitmap.Config.ALPHA_8, true);
     }
-
-    private class OutlineGeneratorCallback implements Runnable {
-
-        private final Bitmap mPreviewSnapshot;
-        private final Context mContext;
-        private final boolean mIsIcon;
-
-        OutlineGeneratorCallback(Bitmap preview) {
-            mPreviewSnapshot = preview;
-            mContext = mView.getContext();
-            mIsIcon = mView instanceof BubbleTextView;
-        }
-
-        @Override
-        public void run() {
-            Bitmap preview = convertPreviewToAlphaBitmap(mPreviewSnapshot);
-            if (mIsIcon) {
-                int size = ActivityContext.lookupContext(mContext).getDeviceProfile().iconSizePx;
-                preview = Bitmap.createScaledBitmap(preview, size, size, false);
-            }
-            //else case covers AppWidgetHost (doesn't drag/drop across different device profiles)
-
-            // We start by removing most of the alpha channel so as to ignore shadows, and
-            // other types of partial transparency when defining the shape of the object
-            byte[] pixels = new byte[preview.getWidth() * preview.getHeight()];
-            ByteBuffer buffer = ByteBuffer.wrap(pixels);
-            buffer.rewind();
-            preview.copyPixelsToBuffer(buffer);
-
-            for (int i = 0; i < pixels.length; i++) {
-                if ((pixels[i] & 0xFF) < 188) {
-                    pixels[i] = 0;
-                }
-            }
-
-            buffer.rewind();
-            preview.copyPixelsFromBuffer(buffer);
-
-            final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
-            Canvas canvas = new Canvas();
-
-            // calculate the outer blur first
-            paint.setMaskFilter(new BlurMaskFilter(blurSizeOutline, BlurMaskFilter.Blur.OUTER));
-            int[] outerBlurOffset = new int[2];
-            Bitmap thickOuterBlur = preview.extractAlpha(paint, outerBlurOffset);
-
-            paint.setMaskFilter(new BlurMaskFilter(
-                    mContext.getResources().getDimension(R.dimen.blur_size_thin_outline),
-                    BlurMaskFilter.Blur.OUTER));
-            int[] brightOutlineOffset = new int[2];
-            Bitmap brightOutline = preview.extractAlpha(paint, brightOutlineOffset);
-
-            // calculate the inner blur
-            canvas.setBitmap(preview);
-            canvas.drawColor(0xFF000000, PorterDuff.Mode.SRC_OUT);
-            paint.setMaskFilter(new BlurMaskFilter(blurSizeOutline, BlurMaskFilter.Blur.NORMAL));
-            int[] thickInnerBlurOffset = new int[2];
-            Bitmap thickInnerBlur = preview.extractAlpha(paint, thickInnerBlurOffset);
-
-            // mask out the inner blur
-            paint.setMaskFilter(null);
-            paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
-            canvas.setBitmap(thickInnerBlur);
-            canvas.drawBitmap(preview, -thickInnerBlurOffset[0],
-                    -thickInnerBlurOffset[1], paint);
-            canvas.drawRect(0, 0, -thickInnerBlurOffset[0], thickInnerBlur.getHeight(), paint);
-            canvas.drawRect(0, 0, thickInnerBlur.getWidth(), -thickInnerBlurOffset[1], paint);
-
-            // draw the inner and outer blur
-            paint.setXfermode(null);
-            canvas.setBitmap(preview);
-            canvas.drawColor(0, PorterDuff.Mode.CLEAR);
-            canvas.drawBitmap(thickInnerBlur, thickInnerBlurOffset[0], thickInnerBlurOffset[1],
-                    paint);
-            canvas.drawBitmap(thickOuterBlur, outerBlurOffset[0], outerBlurOffset[1], paint);
-
-            // draw the bright outline
-            canvas.drawBitmap(brightOutline, brightOutlineOffset[0], brightOutlineOffset[1], paint);
-
-            // cleanup
-            canvas.setBitmap(null);
-            brightOutline.recycle();
-            thickOuterBlur.recycle();
-            thickInnerBlur.recycle();
-
-            generatedDragOutline = preview;
-        }
-    }
 }
diff --git a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
index fc8d855..c28bab5 100644
--- a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
+++ b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
@@ -26,7 +26,6 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile.GridOption;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.Executors;
 
 /**
@@ -143,11 +142,9 @@
             }
             case ICON_THEMED:
             case SET_ICON_THEMED: {
-                if (FeatureFlags.ENABLE_THEMED_ICONS.get()) {
-                    getPrefs(getContext()).edit()
-                            .putBoolean(KEY_THEMED_ICONS, values.getAsBoolean(BOOLEAN_VALUE))
-                            .apply();
-                }
+                getPrefs(getContext()).edit()
+                        .putBoolean(KEY_THEMED_ICONS, values.getAsBoolean(BOOLEAN_VALUE))
+                        .apply();
                 return 1;
             }
             default:
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index d5bcb0c..c1bab54 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -20,6 +20,7 @@
 import static android.view.View.MeasureSpec.makeMeasureSpec;
 import static android.view.View.VISIBLE;
 
+import static com.android.launcher3.DeviceProfile.DEFAULT_SCALE;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
 import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
 import static com.android.launcher3.model.ModelUtils.getMissingHotseatRanks;
@@ -36,6 +37,7 @@
 import android.content.Intent;
 import android.content.res.TypedArray;
 import android.graphics.Color;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.drawable.AdaptiveIconDrawable;
 import android.graphics.drawable.ColorDrawable;
@@ -43,6 +45,8 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.util.AttributeSet;
+import android.util.Size;
+import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
@@ -53,6 +57,9 @@
 import android.view.WindowManager;
 import android.widget.TextClock;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
@@ -96,6 +103,7 @@
 import com.android.launcher3.widget.LocalColorExtractor;
 import com.android.launcher3.widget.NavigableAppWidgetHostView;
 import com.android.launcher3.widget.custom.CustomWidgetManager;
+import com.android.launcher3.widget.util.WidgetSizes;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -169,6 +177,7 @@
     private final Context mContext;
     private final InvariantDeviceProfile mIdp;
     private final DeviceProfile mDp;
+    private final DeviceProfile mDpOrig;
     private final Rect mInsets;
     private final WorkspaceItemInfo mWorkspaceItemInfo;
     private final LayoutInflater mHomeElementInflater;
@@ -177,16 +186,27 @@
     private final Map<Integer, CellLayout> mWorkspaceScreens = new HashMap<>();
     private final AppWidgetHost mAppWidgetHost;
     private final SparseIntArray mWallpaperColorResources;
+    private final SparseArray<Size> mLauncherWidgetSpanInfo;
 
     public LauncherPreviewRenderer(Context context,
             InvariantDeviceProfile idp,
-            WallpaperColors wallpaperColorsOverride) {
+            WallpaperColors wallpaperColorsOverride,
+            @Nullable final SparseArray<Size> launcherWidgetSpanInfo) {
 
         super(context);
         mUiHandler = new Handler(Looper.getMainLooper());
         mContext = context;
         mIdp = idp;
-        mDp = idp.getDeviceProfile(context).copy(context);
+        mDp = idp.getDeviceProfile(context).toBuilder(context).setViewScaleProvider(
+                this::getAppWidgetScale).build();
+        if (context instanceof PreviewContext) {
+            Context tempContext = ((PreviewContext) context).getBaseContext();
+            mDpOrig = new InvariantDeviceProfile(tempContext, InvariantDeviceProfile
+                    .getCurrentGridName(tempContext)).getDeviceProfile(tempContext)
+                    .copy(tempContext);
+        } else {
+            mDpOrig = mDp;
+        }
 
         WindowInsets currentWindowInsets = context.getSystemService(WindowManager.class)
                 .getCurrentWindowMetrics().getWindowInsets();
@@ -224,6 +244,9 @@
         mHotseat = mRootView.findViewById(R.id.hotseat);
         mHotseat.resetLayout(false);
 
+        mLauncherWidgetSpanInfo = launcherWidgetSpanInfo == null ? new SparseArray<>() :
+                launcherWidgetSpanInfo;
+
         CellLayout firstScreen = mRootView.findViewById(R.id.workspace);
         firstScreen.setPadding(mDp.workspacePadding.left + mDp.cellLayoutPaddingPx.left,
                 mDp.workspacePadding.top + mDp.cellLayoutPaddingPx.top,
@@ -381,6 +404,41 @@
         addInScreenFromBind(view, info);
     }
 
+    @NonNull
+    private PointF getAppWidgetScale(@Nullable ItemInfo itemInfo) {
+        if (!(itemInfo instanceof LauncherAppWidgetInfo)) {
+            return DEFAULT_SCALE;
+        }
+        LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) itemInfo;
+        final Size launcherWidgetSize = mLauncherWidgetSpanInfo.get(info.appWidgetId);
+        if (launcherWidgetSize == null) {
+            return DEFAULT_SCALE;
+        }
+        final Size origSize = WidgetSizes.getWidgetSizePx(mDpOrig,
+                launcherWidgetSize.getWidth(), launcherWidgetSize.getHeight());
+        final Size newSize = WidgetSizes.getWidgetSizePx(mDp, info.spanX, info.spanY);
+        final Rect previewInset = new Rect();
+        final Rect origInset = new Rect();
+        // When the setup() is called for the LayoutParams, insets are added to the width
+        // and height of the view. This is not accounted for in WidgetSizes and is handled
+        // here.
+        if (mDp.shouldInsetWidgets()) {
+            previewInset.set(mDp.inv.defaultWidgetPadding);
+        } else {
+            previewInset.setEmpty();
+        }
+        if (mDpOrig.shouldInsetWidgets()) {
+            origInset.set(mDpOrig.inv.defaultWidgetPadding);
+        } else {
+            origInset.setEmpty();
+        }
+
+        return new PointF((float) newSize.getWidth() / (origSize.getWidth()
+                + origInset.left + origInset.right),
+                (float) newSize.getHeight() / (origSize.getHeight()
+                + origInset.top + origInset.bottom));
+    }
+
     private void inflateAndAddPredictedIcon(WorkspaceItemInfo info) {
         CellLayout screen = mWorkspaceScreens.get(info.screenId);
         View view = PredictedAppIconInflater.inflate(mHomeElementInflater, screen, info);
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/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
index fd11b37..287b976 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -22,10 +22,13 @@
 import android.app.WallpaperColors;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
+import android.database.Cursor;
 import android.hardware.display.DisplayManager;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.util.Log;
+import android.util.Size;
+import android.util.SparseArray;
 import android.view.ContextThemeWrapper;
 import android.view.Display;
 import android.view.SurfaceControlViewHost;
@@ -34,6 +37,8 @@
 import android.view.WindowManager.LayoutParams;
 import android.view.animation.AccelerateDecelerateInterpolator;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 import androidx.annotation.WorkerThread;
 
@@ -124,6 +129,45 @@
     }
 
     /**
+     * A function that queries for the launcher app widget span info
+     *
+     * @param context The context to get the content resolver from, should be related to launcher
+     * @return A SparseArray with the app widget id being the key and the span info being the values
+     */
+    @WorkerThread
+    @Nullable
+    public SparseArray<Size> getLoadedLauncherWidgetInfo(
+            @NonNull final Context context) {
+        final SparseArray<Size> widgetInfo = new SparseArray<>();
+        final String query = LauncherSettings.Favorites.ITEM_TYPE + " = "
+                + LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
+
+        try (Cursor c = context.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
+                new String[] {
+                        LauncherSettings.Favorites.APPWIDGET_ID,
+                        LauncherSettings.Favorites.SPANX,
+                        LauncherSettings.Favorites.SPANY
+                }, query, null, null)) {
+            final int appWidgetIdIndex = c.getColumnIndexOrThrow(
+                    LauncherSettings.Favorites.APPWIDGET_ID);
+            final int spanXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANX);
+            final int spanYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANY);
+            while (c.moveToNext()) {
+                final int appWidgetId = c.getInt(appWidgetIdIndex);
+                final int spanX = c.getInt(spanXIndex);
+                final int spanY = c.getInt(spanYIndex);
+
+                widgetInfo.append(appWidgetId, new Size(spanX, spanY));
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Error querying for launcher widget info", e);
+            return null;
+        }
+
+        return widgetInfo;
+    }
+
+    /**
      * Generates the preview in background
      */
     public void loadAsync() {
@@ -174,8 +218,11 @@
                     loadWorkspace(new ArrayList<>(), LauncherSettings.Favorites.PREVIEW_CONTENT_URI,
                             query);
 
+                    final SparseArray<Size> spanInfo =
+                            getLoadedLauncherWidgetInfo(previewContext.getBaseContext());
+
                     MAIN_EXECUTOR.execute(() -> {
-                        renderView(previewContext, mBgDataModel, mWidgetProvidersMap);
+                        renderView(previewContext, mBgDataModel, mWidgetProvidersMap, spanInfo);
                         mOnDestroyCallbacks.add(previewContext::onDestroy);
                     });
                 }
@@ -183,7 +230,8 @@
         } else {
             LauncherAppState.getInstance(inflationContext).getModel().loadAsync(dataModel -> {
                 if (dataModel != null) {
-                    MAIN_EXECUTOR.execute(() -> renderView(inflationContext, dataModel, null));
+                    MAIN_EXECUTOR.execute(() -> renderView(inflationContext, dataModel, null,
+                            null));
                 } else {
                     Log.e(TAG, "Model loading failed");
                 }
@@ -201,12 +249,13 @@
 
     @UiThread
     private void renderView(Context inflationContext, BgDataModel dataModel,
-            Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap) {
+            Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap,
+            @Nullable final SparseArray<Size> launcherWidgetSpanInfo) {
         if (mDestroyed) {
             return;
         }
-        View view = new LauncherPreviewRenderer(inflationContext, mIdp, mWallpaperColors)
-                .getRenderedView(dataModel, widgetProviderInfoMap);
+        View view = new LauncherPreviewRenderer(inflationContext, mIdp, mWallpaperColors,
+                launcherWidgetSpanInfo).getRenderedView(dataModel, widgetProviderInfoMap);
         // This aspect scales the view to fit in the surface and centers it
         final float scale = Math.min(mWidth / (float) view.getMeasuredWidth(),
                 mHeight / (float) view.getMeasuredHeight());
diff --git a/src/com/android/launcher3/graphics/ShadowDrawable.java b/src/com/android/launcher3/graphics/ShadowDrawable.java
deleted file mode 100644
index d8a7070..0000000
--- a/src/com/android/launcher3/graphics/ShadowDrawable.java
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- * Copyright (C) 2017 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.graphics;
-
-import android.annotation.TargetApi;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.BlurMaskFilter;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorFilter;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.util.AttributeSet;
-
-import com.android.launcher3.R;
-import com.android.launcher3.icons.BitmapRenderer;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-
-/**
- * A drawable which adds shadow around a child drawable.
- */
-@TargetApi(Build.VERSION_CODES.O)
-public class ShadowDrawable extends Drawable {
-
-    private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
-
-    private final ShadowDrawableState mState;
-
-    @SuppressWarnings("unused")
-    public ShadowDrawable() {
-        this(new ShadowDrawableState());
-    }
-
-    private ShadowDrawable(ShadowDrawableState state) {
-        mState = state;
-    }
-
-    @Override
-    public void draw(Canvas canvas) {
-        Rect bounds = getBounds();
-        if (bounds.isEmpty()) {
-            return;
-        }
-        if (mState.mLastDrawnBitmap == null) {
-            regenerateBitmapCache();
-        }
-        canvas.drawBitmap(mState.mLastDrawnBitmap, null, bounds, mPaint);
-    }
-
-    @Override
-    public void setAlpha(int alpha) {
-        mPaint.setAlpha(alpha);
-        invalidateSelf();
-    }
-
-    @Override
-    public void setColorFilter(ColorFilter colorFilter) {
-        mPaint.setColorFilter(colorFilter);
-        invalidateSelf();
-    }
-
-    @Override
-    public ConstantState getConstantState() {
-        return mState;
-    }
-
-    @Override
-    public int getOpacity() {
-        return PixelFormat.TRANSLUCENT;
-    }
-
-    @Override
-    public int getIntrinsicHeight() {
-        return mState.mIntrinsicHeight;
-    }
-
-    @Override
-    public int getIntrinsicWidth() {
-        return mState.mIntrinsicWidth;
-    }
-
-    @Override
-    public boolean canApplyTheme() {
-        return mState.canApplyTheme();
-    }
-
-    @Override
-    public void applyTheme(Resources.Theme t) {
-        TypedArray ta = t.obtainStyledAttributes(new int[] {R.attr.isWorkspaceDarkText});
-        boolean isDark = ta.getBoolean(0, false);
-        ta.recycle();
-        if (mState.mIsDark != isDark) {
-            mState.mIsDark = isDark;
-            mState.mLastDrawnBitmap = null;
-            invalidateSelf();
-        }
-    }
-
-    private void regenerateBitmapCache() {
-        // Call mutate, so that the pixel allocation by the underlying vector drawable is cleared.
-        Drawable d = mState.mChildState.newDrawable().mutate();
-        d.setBounds(mState.mShadowSize, mState.mShadowSize,
-                mState.mIntrinsicWidth - mState.mShadowSize,
-                mState.mIntrinsicHeight - mState.mShadowSize);
-        d.setTint(mState.mIsDark ? mState.mDarkTintColor : Color.WHITE);
-
-        if (mState.mIsDark) {
-            // Dark text do not have any shadow, but just the bitmap
-            mState.mLastDrawnBitmap = BitmapRenderer.createHardwareBitmap(
-                    mState.mIntrinsicWidth, mState.mIntrinsicHeight, d::draw);
-        } else {
-            Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
-            paint.setMaskFilter(new BlurMaskFilter(mState.mShadowSize, BlurMaskFilter.Blur.NORMAL));
-
-            // Generate the shadow bitmap
-            int[] offset = new int[2];
-            Bitmap shadow = BitmapRenderer.createSoftwareBitmap(
-                    mState.mIntrinsicWidth, mState.mIntrinsicHeight, d::draw)
-                    .extractAlpha(paint, offset);
-
-            paint.setMaskFilter(null);
-            paint.setColor(mState.mShadowColor);
-            mState.mLastDrawnBitmap = BitmapRenderer.createHardwareBitmap(
-                    mState.mIntrinsicWidth, mState.mIntrinsicHeight, c -> {
-                        c.drawBitmap(shadow, offset[0], offset[1], paint);
-                        d.draw(c);
-                    });
-        }
-    }
-
-    @Override
-    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs,
-            Resources.Theme theme) throws XmlPullParserException, IOException {
-        super.inflate(r, parser, attrs, theme);
-
-        final TypedArray a = theme == null
-                ? r.obtainAttributes(attrs, R.styleable.ShadowDrawable)
-                : theme.obtainStyledAttributes(attrs, R.styleable.ShadowDrawable, 0, 0);
-        try {
-            Drawable d = a.getDrawable(R.styleable.ShadowDrawable_android_src);
-            if (d == null) {
-                throw new XmlPullParserException("missing src attribute");
-            }
-            mState.mShadowColor = a.getColor(
-                    R.styleable.ShadowDrawable_android_shadowColor, Color.BLACK);
-            mState.mShadowSize = a.getDimensionPixelSize(
-                    R.styleable.ShadowDrawable_android_elevation, 0);
-            mState.mDarkTintColor = a.getColor(
-                    R.styleable.ShadowDrawable_darkTintColor, Color.BLACK);
-
-            mState.mIntrinsicHeight = d.getIntrinsicHeight() + 2 * mState.mShadowSize;
-            mState.mIntrinsicWidth = d.getIntrinsicWidth() + 2 * mState.mShadowSize;
-            mState.mChangingConfigurations = d.getChangingConfigurations();
-
-            mState.mChildState = d.getConstantState();
-        } finally {
-            a.recycle();
-        }
-    }
-
-    private static class ShadowDrawableState extends ConstantState {
-
-        int mChangingConfigurations;
-        int mIntrinsicWidth;
-        int mIntrinsicHeight;
-
-        int mShadowColor;
-        int mShadowSize;
-        int mDarkTintColor;
-
-        boolean mIsDark;
-        Bitmap mLastDrawnBitmap;
-        ConstantState mChildState;
-
-        @Override
-        public Drawable newDrawable() {
-            return new ShadowDrawable(this);
-        }
-
-        @Override
-        public int getChangingConfigurations() {
-            return mChangingConfigurations;
-        }
-
-        @Override
-        public boolean canApplyTheme() {
-            return true;
-        }
-    }
-}
diff --git a/src/com/android/launcher3/graphics/SysUiScrim.java b/src/com/android/launcher3/graphics/SysUiScrim.java
index f0766c5..185b8d3 100644
--- a/src/com/android/launcher3/graphics/SysUiScrim.java
+++ b/src/com/android/launcher3/graphics/SysUiScrim.java
@@ -43,7 +43,7 @@
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.R;
-import com.android.launcher3.ResourceUtils;
+import com.android.launcher3.testing.shared.ResourceUtils;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.DynamicResource;
diff --git a/src/com/android/launcher3/icons/ComponentWithLabel.java b/src/com/android/launcher3/icons/ComponentWithLabel.java
index 372c591..30575fc 100644
--- a/src/com/android/launcher3/icons/ComponentWithLabel.java
+++ b/src/com/android/launcher3/icons/ComponentWithLabel.java
@@ -20,6 +20,8 @@
 import android.content.pm.PackageManager;
 import android.os.UserHandle;
 
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.icons.cache.CachingLogic;
 
 public interface ComponentWithLabel {
@@ -42,22 +44,26 @@
         }
 
         @Override
-        public ComponentName getComponent(T object) {
+        @NonNull
+        public ComponentName getComponent(@NonNull T object) {
             return object.getComponent();
         }
 
+        @NonNull
         @Override
-        public UserHandle getUser(T object) {
+        public UserHandle getUser(@NonNull T object) {
             return object.getUser();
         }
 
+        @NonNull
         @Override
-        public CharSequence getLabel(T object) {
+        public CharSequence getLabel(@NonNull T object) {
             return object.getLabel(mPackageManager);
         }
 
+        @NonNull
         @Override
-        public BitmapInfo loadIcon(Context context, T object) {
+        public BitmapInfo loadIcon(@NonNull Context context, @NonNull T object) {
             return BitmapInfo.LOW_RES_INFO;
         }
 
diff --git a/src/com/android/launcher3/icons/ComponentWithLabelAndIcon.java b/src/com/android/launcher3/icons/ComponentWithLabelAndIcon.java
index c8606b1..0a52dd7 100644
--- a/src/com/android/launcher3/icons/ComponentWithLabelAndIcon.java
+++ b/src/com/android/launcher3/icons/ComponentWithLabelAndIcon.java
@@ -41,7 +41,8 @@
 
         @NonNull
         @Override
-        public BitmapInfo loadIcon(Context context, ComponentWithLabelAndIcon object) {
+        public BitmapInfo loadIcon(@NonNull Context context,
+                @NonNull ComponentWithLabelAndIcon object) {
             Drawable d = object.getFullResIcon(LauncherAppState.getInstance(context)
                     .getIconCache());
             if (d == null) {
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index fe9b633..0b4a4a5 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -51,7 +51,6 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherFiles;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.ComponentWithLabel.ComponentCachingLogic;
 import com.android.launcher3.icons.cache.BaseIconCache;
 import com.android.launcher3.icons.cache.CachingLogic;
@@ -119,15 +118,16 @@
     }
 
     @Override
-    protected long getSerialNumberForUser(UserHandle user) {
+    protected long getSerialNumberForUser(@NonNull UserHandle user) {
         return mUserManager.getSerialNumberForUser(user);
     }
 
     @Override
-    protected boolean isInstantApp(ApplicationInfo info) {
+    protected boolean isInstantApp(@NonNull ApplicationInfo info) {
         return mInstantAppResolver.isInstantApp(info);
     }
 
+    @NonNull
     @Override
     public BaseIconFactory getIconFactory() {
         return LauncherIcons.obtain(mContext);
@@ -136,7 +136,8 @@
     /**
      * Updates the entries related to the given package in memory and persistent DB.
      */
-    public synchronized void updateIconsForPkg(String packageName, UserHandle user) {
+    public synchronized void updateIconsForPkg(@NonNull final String packageName,
+            @NonNull final UserHandle user) {
         removeIconsForPkg(packageName, user);
         try {
             PackageInfo info = mPackageManager.getPackageInfo(packageName,
@@ -231,14 +232,8 @@
      */
     public <T extends ItemInfoWithIcon> void getShortcutIcon(T info, ShortcutInfo si,
             @NonNull Predicate<T> fallbackIconCheck) {
-        BitmapInfo bitmapInfo;
-        if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
-            bitmapInfo = cacheLocked(ShortcutKey.fromInfo(si).componentName, si.getUserHandle(),
-                    () -> si, mShortcutCachingLogic, false, false).bitmap;
-        } else {
-            // If caching is disabled, load the full icon
-            bitmapInfo = mShortcutCachingLogic.loadIcon(mContext, si);
-        }
+        BitmapInfo bitmapInfo = cacheLocked(ShortcutKey.fromInfo(si).componentName,
+                si.getUserHandle(), () -> si, mShortcutCachingLogic, false, false).bitmap;
         if (bitmapInfo.isNullOrLowRes()) {
             bitmapInfo = getDefaultIcon(si.getUserHandle());
         }
@@ -478,7 +473,7 @@
      * Fill in {@param infoInOut} with the corresponding icon and label.
      */
     public synchronized void getTitleAndIconForApp(
-            PackageItemInfo infoInOut, boolean useLowResIcon) {
+            @NonNull final PackageItemInfo infoInOut, final boolean useLowResIcon) {
         CacheEntry entry = getEntryForPackageLocked(
                 infoInOut.packageName, infoInOut.user, useLowResIcon);
         applyCacheEntry(entry, infoInOut);
@@ -517,10 +512,16 @@
         return bitmap.withFlags(getUserFlagOpLocked(user));
     }
 
-    protected void applyCacheEntry(CacheEntry entry, ItemInfoWithIcon info) {
+    protected void applyCacheEntry(@NonNull final CacheEntry entry,
+            @NonNull final ItemInfoWithIcon info) {
         info.title = Utilities.trim(entry.title);
         info.contentDescription = entry.contentDescription;
-        info.bitmap = (entry.bitmap == null) ? getDefaultIcon(info.user) : entry.bitmap;
+        info.bitmap = entry.bitmap;
+        if (entry.bitmap == null) {
+            // TODO: entry.bitmap can never be null, so this should not happen at all.
+            Log.wtf(TAG, "Cannot find bitmap from the cache, default icon was loaded.");
+            info.bitmap = getDefaultIcon(info.user);
+        }
     }
 
     public Drawable getFullResIcon(LauncherActivityInfo info) {
@@ -533,6 +534,7 @@
     }
 
     @Override
+    @NonNull
     protected String getIconSystemState(String packageName) {
         return mIconProvider.getSystemStateForPackage(mSystemState, packageName);
     }
diff --git a/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java b/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java
index 4b8c1ad..406f697 100644
--- a/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java
+++ b/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java
@@ -20,6 +20,8 @@
 import android.content.pm.LauncherActivityInfo;
 import android.os.UserHandle;
 
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.icons.BaseIconFactory.IconOptions;
@@ -40,23 +42,27 @@
                 R.string.launcher_activity_logic_class);
     }
 
+    @NonNull
     @Override
-    public ComponentName getComponent(LauncherActivityInfo object) {
+    public ComponentName getComponent(@NonNull LauncherActivityInfo object) {
         return object.getComponentName();
     }
 
+    @NonNull
     @Override
-    public UserHandle getUser(LauncherActivityInfo object) {
+    public UserHandle getUser(@NonNull LauncherActivityInfo object) {
         return object.getUser();
     }
 
+    @NonNull
     @Override
-    public CharSequence getLabel(LauncherActivityInfo object) {
+    public CharSequence getLabel(@NonNull LauncherActivityInfo object) {
         return object.getLabel();
     }
 
+    @NonNull
     @Override
-    public BitmapInfo loadIcon(Context context, LauncherActivityInfo object) {
+    public BitmapInfo loadIcon(@NonNull Context context, @NonNull LauncherActivityInfo object) {
         try (LauncherIcons li = LauncherIcons.obtain(context)) {
             return li.createBadgedIconBitmap(LauncherAppState.getInstance(context)
                             .getIconProvider().getIcon(object, li.mFillResIconDpi),
diff --git a/src/com/android/launcher3/icons/ShortcutCachingLogic.java b/src/com/android/launcher3/icons/ShortcutCachingLogic.java
index 6a8f34a..d5a79dd 100644
--- a/src/com/android/launcher3/icons/ShortcutCachingLogic.java
+++ b/src/com/android/launcher3/icons/ShortcutCachingLogic.java
@@ -16,7 +16,7 @@
 
 package com.android.launcher3.icons;
 
-import static com.android.launcher3.model.WidgetsModel.GO_DISABLE_WIDGETS;
+import static com.android.launcher3.model.WidgetsModel.GO_DISABLE_SHORTCUTS;
 
 import android.content.ComponentName;
 import android.content.Context;
@@ -29,9 +29,9 @@
 import android.util.Log;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.cache.CachingLogic;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.Themes;
@@ -44,29 +44,34 @@
     private static final String TAG = "ShortcutCachingLogic";
 
     @Override
-    public ComponentName getComponent(ShortcutInfo info) {
+    @NonNull
+    public ComponentName getComponent(@NonNull ShortcutInfo info) {
         return ShortcutKey.fromInfo(info).componentName;
     }
 
+    @NonNull
     @Override
-    public UserHandle getUser(ShortcutInfo info) {
+    public UserHandle getUser(@NonNull ShortcutInfo info) {
         return info.getUserHandle();
     }
 
+    @NonNull
     @Override
-    public CharSequence getLabel(ShortcutInfo info) {
+    public CharSequence getLabel(@NonNull ShortcutInfo info) {
         return info.getShortLabel();
     }
 
     @Override
-    public CharSequence getDescription(ShortcutInfo object, CharSequence fallback) {
+    @NonNull
+    public CharSequence getDescription(@NonNull ShortcutInfo object,
+            @NonNull CharSequence fallback) {
         CharSequence label = object.getLongLabel();
         return TextUtils.isEmpty(label) ? fallback : label;
     }
 
     @NonNull
     @Override
-    public BitmapInfo loadIcon(Context context, ShortcutInfo info) {
+    public BitmapInfo loadIcon(@NonNull Context context, @NonNull ShortcutInfo info) {
         try (LauncherIcons li = LauncherIcons.obtain(context)) {
             Drawable unbadgedDrawable = ShortcutCachingLogic.getIcon(
                     context, info, LauncherAppState.getIDP(context).fillResIconDpi);
@@ -77,8 +82,9 @@
     }
 
     @Override
-    public long getLastUpdatedTime(ShortcutInfo shortcutInfo, PackageInfo info) {
-        if (shortcutInfo == null || !FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
+    public long getLastUpdatedTime(@Nullable ShortcutInfo shortcutInfo,
+            @NonNull PackageInfo info) {
+        if (shortcutInfo == null) {
             return info.lastUpdateTime;
         }
         return Math.max(shortcutInfo.getLastChangedTimestamp(), info.lastUpdateTime);
@@ -94,7 +100,7 @@
      * Launcher specific checks
      */
     public static Drawable getIcon(Context context, ShortcutInfo shortcutInfo, int density) {
-        if (GO_DISABLE_WIDGETS) {
+        if (GO_DISABLE_SHORTCUTS) {
             return null;
         }
         try {
diff --git a/src/com/android/launcher3/logging/EventLogArray.java b/src/com/android/launcher3/logging/EventLogArray.java
deleted file mode 100644
index 3ecfb23..0000000
--- a/src/com/android/launcher3/logging/EventLogArray.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright (C) 2019 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.logging;
-
-
-import android.util.Log;
-import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.Locale;
-import java.util.Random;
-
-/**
- * A utility class to record and log events. Events are stored in a fixed size array and old logs
- * are purged as new events come.
- */
-public class EventLogArray {
-
-    private static final int TYPE_ONE_OFF = 0;
-    private static final int TYPE_FLOAT = 1;
-    private static final int TYPE_INTEGER = 2;
-    private static final int TYPE_BOOL_TRUE = 3;
-    private static final int TYPE_BOOL_FALSE = 4;
-
-    private final String name;
-    private final EventEntry[] logs;
-    private int nextIndex;
-    private int mLogId;
-
-    public EventLogArray(String name, int size) {
-        this.name = name;
-        logs = new EventEntry[size];
-        nextIndex = 0;
-    }
-
-    public void addLog(String event) {
-        addLog(TYPE_ONE_OFF, event, 0);
-    }
-
-    public void addLog(String event, int extras) {
-        addLog(TYPE_INTEGER, event, extras);
-    }
-
-    public void addLog(String event, boolean extras) {
-        addLog(extras ? TYPE_BOOL_TRUE : TYPE_BOOL_FALSE, event, 0);
-    }
-
-    private void addLog(int type, String event, float extras) {
-        // Merge the logs if its a duplicate
-        int last = (nextIndex + logs.length - 1) % logs.length;
-        int secondLast = (nextIndex + logs.length - 2) % logs.length;
-        if (isEntrySame(logs[last], type, event) && isEntrySame(logs[secondLast], type, event)) {
-            logs[last].update(type, event, extras, mLogId);
-            logs[secondLast].duplicateCount++;
-            return;
-        }
-
-        if (logs[nextIndex] == null) {
-            logs[nextIndex] = new EventEntry();
-        }
-        logs[nextIndex].update(type, event, extras, mLogId);
-        nextIndex = (nextIndex + 1) % logs.length;
-    }
-
-    public void clear() {
-        Arrays.setAll(logs, (i) -> null);
-    }
-
-    public void dump(String prefix, PrintWriter writer) {
-        writer.println(prefix + "EventLog (" + name + ") history:");
-        SimpleDateFormat sdf = new SimpleDateFormat("  HH:mm:ss.SSSZ  ", Locale.US);
-        Date date = new Date();
-
-        for (int i = 0; i < logs.length; i++) {
-            EventEntry log = logs[(nextIndex + logs.length - i - 1) % logs.length];
-            if (log == null) {
-                continue;
-            }
-            date.setTime(log.time);
-
-            StringBuilder msg = new StringBuilder(prefix).append(sdf.format(date))
-                    .append(log.event);
-            switch (log.type) {
-                case TYPE_BOOL_FALSE:
-                    msg.append(": false");
-                    break;
-                case TYPE_BOOL_TRUE:
-                    msg.append(": true");
-                    break;
-                case TYPE_FLOAT:
-                    msg.append(": ").append(log.extras);
-                    break;
-                case TYPE_INTEGER:
-                    msg.append(": ").append((int) log.extras);
-                    break;
-                default: // fall out
-            }
-            if (log.duplicateCount > 0) {
-                msg.append(" & ").append(log.duplicateCount).append(" similar events");
-            }
-            msg.append(" traceId: ").append(log.traceId);
-            writer.println(msg);
-        }
-    }
-
-    /** Returns a 3 digit random number between 100-999 */
-    public int generateAndSetLogId() {
-        Random r = new Random();
-        mLogId = r.nextInt(900) + 100;
-        return mLogId;
-    }
-
-    private boolean isEntrySame(EventEntry entry, int type, String event) {
-        return entry != null && entry.type == type && entry.event.equals(event);
-    }
-
-    /** A single event entry. */
-    private static class EventEntry {
-
-        private int type;
-        private String event;
-        private float extras;
-        private long time;
-        private int duplicateCount;
-        private int traceId;
-
-        public void update(int type, String event, float extras, int traceId) {
-            this.type = type;
-            this.event = event;
-            this.extras = extras;
-            this.traceId = traceId;
-            time = System.currentTimeMillis();
-            duplicateCount = 0;
-        }
-    }
-}
diff --git a/src/com/android/launcher3/logging/KeyboardStateManager.java b/src/com/android/launcher3/logging/KeyboardStateManager.java
new file mode 100644
index 0000000..3103af1
--- /dev/null
+++ b/src/com/android/launcher3/logging/KeyboardStateManager.java
@@ -0,0 +1,61 @@
+/*
+ * 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.logging;
+
+import static com.android.launcher3.logging.KeyboardStateManager.KeyboardState.NO_IME_ACTION;
+
+import android.os.SystemClock;
+
+/**
+ * Class to maintain keyboard states.
+ */
+public class KeyboardStateManager {
+    private long mUpdatedTime;
+
+    public enum KeyboardState {
+        NO_IME_ACTION,
+        SHOW,
+        HIDE,
+    }
+
+    private KeyboardState mKeyboardState;
+
+    public KeyboardStateManager() {
+        mKeyboardState = NO_IME_ACTION;
+    }
+
+    /**
+     * Returns time when keyboard state was updated.
+     */
+    public long getLastUpdatedTime() {
+        return mUpdatedTime;
+    }
+
+    /**
+     * Returns current keyboard state.
+     */
+    public KeyboardState getKeyboardState() {
+        return mKeyboardState;
+    }
+
+    /**
+     * Setter method to set keyboard state.
+     */
+    public void setKeyboardState(KeyboardState keyboardState) {
+        mUpdatedTime = SystemClock.elapsedRealtime();
+        mKeyboardState = keyboardState;
+    }
+}
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index c4ec4e3..22627b4 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -56,6 +56,7 @@
     private InstanceId mInstanceId;
 
     protected @Nullable ActivityContext mActivityContext = null;
+    private KeyboardStateManager mKeyboardStateManager;
 
     /**
      * Returns event enum based on the two state transition information when swipe
@@ -592,6 +593,12 @@
 
         @UiEvent(doc = "User tapped on Share app system shortcut.")
         LAUNCHER_SYSTEM_SHORTCUT_APP_SHARE_TAP(1075),
+
+        @UiEvent(doc = "User has invoked split to right half from an app icon menu")
+        LAUNCHER_APP_ICON_MENU_SPLIT_RIGHT_BOTTOM(1199),
+
+        @UiEvent(doc = "User has invoked split to left half from an app icon menu")
+        LAUNCHER_APP_ICON_MENU_SPLIT_LEFT_TOP(1200)
         ;
 
         // ADD MORE
@@ -737,8 +744,9 @@
             HOT(2),
             TIMEOUT(3),
             FAIL(4),
-            COLD_USERWAITING(5);
-
+            COLD_USERWAITING(5),
+            ATOMIC(6),
+            CONTROLLED(7);
             private final int mId;
 
             LatencyType(int id) {
@@ -816,6 +824,16 @@
         return logger;
     }
 
+    /**
+     * Returns a singleton KeyboardStateManager.
+     */
+    public KeyboardStateManager keyboardStateManager() {
+        if (mKeyboardStateManager == null) {
+            mKeyboardStateManager = new KeyboardStateManager();
+        }
+        return mKeyboardStateManager;
+    }
+
     protected StatsLogger createLogger() {
         return new StatsLogger() {
         };
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index 31ef2e5..0d978e1 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -23,6 +23,9 @@
 import android.util.Log;
 import android.util.Pair;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel.CallbackTask;
 import com.android.launcher3.LauncherSettings;
@@ -36,12 +39,13 @@
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.pm.InstallSessionHelper;
 import com.android.launcher3.pm.PackageInstallInfo;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.PackageManagerHelper;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * Task to add auto-created workspace items.
@@ -50,14 +54,16 @@
 
     private static final String LOG = "AddWorkspaceItemsTask";
 
+    @NonNull
     private final List<Pair<ItemInfo, Object>> mItemList;
 
+    @NonNull
     private final WorkspaceItemSpaceFinder mItemSpaceFinder;
 
     /**
      * @param itemList items to add on the workspace
      */
-    public AddWorkspaceItemsTask(List<Pair<ItemInfo, Object>> itemList) {
+    public AddWorkspaceItemsTask(@NonNull final List<Pair<ItemInfo, Object>> itemList) {
         this(itemList, new WorkspaceItemSpaceFinder());
     }
 
@@ -65,14 +71,15 @@
      * @param itemList items to add on the workspace
      * @param itemSpaceFinder inject WorkspaceItemSpaceFinder dependency for testing
      */
-    public AddWorkspaceItemsTask(List<Pair<ItemInfo, Object>> itemList,
-            WorkspaceItemSpaceFinder itemSpaceFinder) {
+    public AddWorkspaceItemsTask(@NonNull final List<Pair<ItemInfo, Object>> itemList,
+            @NonNull final WorkspaceItemSpaceFinder itemSpaceFinder) {
         mItemList = itemList;
         mItemSpaceFinder = itemSpaceFinder;
     }
 
     @Override
-    public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+    public void execute(@NonNull final LauncherAppState app, @NonNull final BgDataModel dataModel,
+            @NonNull final AllAppsList apps) {
         if (mItemList.isEmpty()) {
             return;
         }
@@ -98,7 +105,8 @@
                     }
 
                     // b/139663018 Short-circuit this logic if the icon is a system app
-                    if (PackageManagerHelper.isSystemApp(app.getContext(), item.getIntent())) {
+                    if (PackageManagerHelper.isSystemApp(app.getContext(),
+                            Objects.requireNonNull(item.getIntent()))) {
                         if (TestProtocol.sDebugTracing) {
                             Log.d(TestProtocol.MISSING_PROMISE_ICON,
                                     LOG + " Item is a system app.");
@@ -159,7 +167,7 @@
                         continue;
                     }
 
-                    List<LauncherActivityInfo> activities = launcherApps
+                    List<LauncherActivityInfo> activities = Objects.requireNonNull(launcherApps)
                             .getActivityList(packageName, item.user);
                     boolean hasActivity = activities != null && !activities.isEmpty();
 
@@ -218,7 +226,7 @@
         if (!addedItemsFinal.isEmpty()) {
             scheduleCallbackTask(new CallbackTask() {
                 @Override
-                public void execute(Callbacks callbacks) {
+                public void execute(@NonNull Callbacks callbacks) {
                     final ArrayList<ItemInfo> addAnimated = new ArrayList<>();
                     final ArrayList<ItemInfo> addNotAnimated = new ArrayList<>();
                     if (!addedItemsFinal.isEmpty()) {
@@ -243,7 +251,8 @@
      * Returns true if the shortcuts already exists on the workspace. This must be called after
      * the workspace has been loaded. We identify a shortcut by its intent.
      */
-    protected boolean shortcutExists(BgDataModel dataModel, Intent intent, UserHandle user) {
+    protected boolean shortcutExists(@NonNull final BgDataModel dataModel,
+            @Nullable final Intent intent, @NonNull final UserHandle user) {
         final String compPkgName, intentWithPkg, intentWithoutPkg;
         if (intent == null) {
             // Skip items with null intents
diff --git a/src/com/android/launcher3/model/AllAppsList.java b/src/com/android/launcher3/model/AllAppsList.java
index 4875d83..6da948c 100644
--- a/src/com/android/launcher3/model/AllAppsList.java
+++ b/src/com/android/launcher3/model/AllAppsList.java
@@ -18,6 +18,7 @@
 
 import static com.android.launcher3.model.data.AppInfo.COMPONENT_KEY_COMPARATOR;
 import static com.android.launcher3.model.data.AppInfo.EMPTY_ARRAY;
+import static com.android.launcher3.testing.shared.TestProtocol.INCORRECT_INFO_UPDATED;
 
 import android.content.ComponentName;
 import android.content.Context;
@@ -38,6 +39,7 @@
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.pm.PackageInstallInfo;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.util.FlagOp;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.SafeCloseable;
@@ -64,7 +66,10 @@
     /** The list off all apps. */
     public final ArrayList<AppInfo> data = new ArrayList<>(DEFAULT_APPLICATIONS_NUMBER);
 
+    @NonNull
     private IconCache mIconCache;
+
+    @NonNull
     private AppFilter mAppFilter;
 
     private boolean mDataChanged = false;
@@ -270,8 +275,14 @@
     }
 
     public void updateIconsAndLabels(HashSet<String> packages, UserHandle user) {
+        if (TestProtocol.sDebugTracing) {
+            Log.i(INCORRECT_INFO_UPDATED, "updateIconsAndLabels: packages=" + packages);
+        }
         for (AppInfo info : data) {
             if (info.user.equals(user) && packages.contains(info.componentName.getPackageName())) {
+                if (TestProtocol.sDebugTracing) {
+                    Log.i(INCORRECT_INFO_UPDATED, "updateIconsAndLabels: updating info=" + info);
+                }
                 mIconCache.updateTitleAndIcon(info);
                 info.sectionName = mIndex.computeSectionName(info.title);
                 mDataChanged = true;
diff --git a/src/com/android/launcher3/model/BaseLoaderResults.java b/src/com/android/launcher3/model/BaseLoaderResults.java
index b50ab58..8c6428b 100644
--- a/src/com/android/launcher3/model/BaseLoaderResults.java
+++ b/src/com/android/launcher3/model/BaseLoaderResults.java
@@ -33,7 +33,7 @@
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.LooperExecutor;
diff --git a/src/com/android/launcher3/model/BaseModelUpdateTask.java b/src/com/android/launcher3/model/BaseModelUpdateTask.java
index 2a6a691..5b6f9f6 100644
--- a/src/com/android/launcher3/model/BaseModelUpdateTask.java
+++ b/src/com/android/launcher3/model/BaseModelUpdateTask.java
@@ -17,6 +17,7 @@
 
 import android.util.Log;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.LauncherAppState;
@@ -47,14 +48,17 @@
     private static final boolean DEBUG_TASKS = false;
     private static final String TAG = "BaseModelUpdateTask";
 
+    // Nullabilities are explicitly omitted here because these are late-init fields,
+    // They will be non-null after init(), which is always the case in enqueueModelUpdateTask().
     private LauncherAppState mApp;
     private LauncherModel mModel;
     private BgDataModel mDataModel;
     private AllAppsList mAllAppsList;
     private Executor mUiExecutor;
 
-    public void init(LauncherAppState app, LauncherModel model,
-            BgDataModel dataModel, AllAppsList allAppsList, Executor uiExecutor) {
+    public void init(@NonNull final LauncherAppState app, @NonNull final LauncherModel model,
+            @NonNull final BgDataModel dataModel, @NonNull final AllAppsList allAppsList,
+            @NonNull final Executor uiExecutor) {
         mApp = app;
         mModel = model;
         mDataModel = dataModel;
@@ -64,7 +68,7 @@
 
     @Override
     public final void run() {
-        if (!mModel.isModelLoaded()) {
+        if (!Objects.requireNonNull(mModel).isModelLoaded()) {
             if (DEBUG_TASKS) {
                 Log.d(TAG, "Ignoring model task since loader is pending=" + this);
             }
@@ -77,13 +81,13 @@
     /**
      * Execute the actual task. Called on the worker thread.
      */
-    public abstract void execute(
-            LauncherAppState app, BgDataModel dataModel, AllAppsList apps);
+    public abstract void execute(@NonNull LauncherAppState app,
+            @NonNull BgDataModel dataModel, @NonNull AllAppsList apps);
 
     /**
      * Schedules a {@param task} to be executed on the current callbacks.
      */
-    public final void scheduleCallbackTask(final CallbackTask task) {
+    public final void scheduleCallbackTask(@NonNull final CallbackTask task) {
         for (final Callbacks cb : mModel.getCallbacks()) {
             mUiExecutor.execute(() -> task.execute(cb));
         }
@@ -95,7 +99,7 @@
         return mModel.getWriter(false /* hasVerticalHotseat */, false /* verifyChanges */, null);
     }
 
-    public void bindUpdatedWorkspaceItems(List<WorkspaceItemInfo> allUpdates) {
+    public void bindUpdatedWorkspaceItems(@NonNull final List<WorkspaceItemInfo> allUpdates) {
         // Bind workspace items
         List<WorkspaceItemInfo> workspaceUpdates = allUpdates.stream()
                 .filter(info -> info.id != ItemInfo.NO_ID)
@@ -113,18 +117,18 @@
                 .forEach(this::bindExtraContainerItems);
     }
 
-    public void bindExtraContainerItems(FixedContainerItems item) {
+    public void bindExtraContainerItems(@NonNull final FixedContainerItems item) {
         FixedContainerItems copy = item.clone();
         scheduleCallbackTask(c -> c.bindExtraContainerItems(copy));
     }
 
-    public void bindDeepShortcuts(BgDataModel dataModel) {
+    public void bindDeepShortcuts(@NonNull final BgDataModel dataModel) {
         final HashMap<ComponentKey, Integer> shortcutMapCopy =
                 new HashMap<>(dataModel.deepShortcutMap);
         scheduleCallbackTask(callbacks -> callbacks.bindDeepShortcutMap(shortcutMapCopy));
     }
 
-    public void bindUpdatedWidgets(BgDataModel dataModel) {
+    public void bindUpdatedWidgets(@NonNull final BgDataModel dataModel) {
         final ArrayList<WidgetsListBaseEntry> widgets =
                 dataModel.widgetsModel.getWidgetsListForPicker(mApp.getContext());
         scheduleCallbackTask(c -> c.bindAllWidgets(widgets));
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index de23c4b..ffb0f2f 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -17,7 +17,7 @@
 
 import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY;
 
-import static com.android.launcher3.model.WidgetsModel.GO_DISABLE_WIDGETS;
+import static com.android.launcher3.model.WidgetsModel.GO_DISABLE_SHORTCUTS;
 import static com.android.launcher3.shortcuts.ShortcutRequest.PINNED;
 
 import static java.util.stream.Collectors.groupingBy;
@@ -286,7 +286,7 @@
      * shortcuts and unpinning any extra shortcuts.
      */
     public synchronized void updateShortcutPinnedState(Context context, UserHandle user) {
-        if (GO_DISABLE_WIDGETS) {
+        if (GO_DISABLE_SHORTCUTS) {
             return;
         }
 
diff --git a/src/com/android/launcher3/model/CacheDataUpdatedTask.java b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
index f644d49..57fefaa 100644
--- a/src/com/android/launcher3/model/CacheDataUpdatedTask.java
+++ b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
@@ -18,6 +18,8 @@
 import android.content.ComponentName;
 import android.os.UserHandle;
 
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.icons.IconCache;
@@ -35,17 +37,23 @@
     public static final int OP_SESSION_UPDATE = 2;
 
     private final int mOp;
+
+    @NonNull
     private final UserHandle mUser;
+
+    @NonNull
     private final HashSet<String> mPackages;
 
-    public CacheDataUpdatedTask(int op, UserHandle user, HashSet<String> packages) {
+    public CacheDataUpdatedTask(final int op, @NonNull final UserHandle user,
+            @NonNull final HashSet<String> packages) {
         mOp = op;
         mUser = user;
         mPackages = packages;
     }
 
     @Override
-    public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+    public void execute(@NonNull final LauncherAppState app, @NonNull final BgDataModel dataModel,
+            @NonNull final AllAppsList apps) {
         IconCache iconCache = app.getIconCache();
         ArrayList<WorkspaceItemInfo> updatedShortcuts = new ArrayList<>();
 
@@ -65,7 +73,7 @@
         bindApplicationsIfNeeded();
     }
 
-    public boolean isValidShortcut(WorkspaceItemInfo si) {
+    public boolean isValidShortcut(@NonNull final WorkspaceItemInfo si) {
         switch (mOp) {
             case OP_CACHE_UPDATE:
                 return true;
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..341372e 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
@@ -31,6 +31,7 @@
 import android.util.ArrayMap;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.InvariantDeviceProfile;
@@ -47,6 +48,7 @@
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.widget.WidgetManagerHelper;
 
+import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -56,6 +58,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 +112,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 +269,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 +777,58 @@
             values.put(LauncherSettings.Favorites.SPANX, spanX);
             values.put(LauncherSettings.Favorites.SPANY, spanY);
         }
+
+        /** 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;
+                case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+                    final String intentStr = cleanIntentString(mIntent);
+                    try {
+                        Intent i = Intent.parseUri(intentStr, 0);
+                        return Objects.requireNonNull(i.getComponent()).toString();
+                    } catch (Exception e) {
+                        return intentStr;
+                    }
+                default:
+                    return cleanIntentString(mIntent);
+            }
+        }
+
+        /**
+         * This method should return an id that should be the same for two folders containing the
+         * same elements.
+         */
+        @NonNull
+        private String getFolderMigrationId() {
+            return mFolderItems.keySet().stream()
+                    .map(intentString -> mFolderItems.get(intentString).size()
+                            + cleanIntentString(intentString))
+                    .sorted()
+                    .collect(Collectors.joining(","));
+        }
+
+        /**
+         * This is needed because sourceBounds can change and make the id of two equal items
+         * different.
+         */
+        @NonNull
+        private String cleanIntentString(@NonNull String intentStr) {
+            try {
+                Intent i = Intent.parseUri(intentStr, 0);
+                i.setSourceBounds(null);
+                return i.toURI();
+            } catch (URISyntaxException e) {
+                Log.e(TAG, "Unable to parse Intent string", e);
+                return intentStr;
+            }
+
+        }
     }
 }
diff --git a/src/com/android/launcher3/model/ItemInstallQueue.java b/src/com/android/launcher3/model/ItemInstallQueue.java
index 5a220f7..69f9b53 100644
--- a/src/com/android/launcher3/model/ItemInstallQueue.java
+++ b/src/com/android/launcher3/model/ItemInstallQueue.java
@@ -49,7 +49,7 @@
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.shortcuts.ShortcutRequest;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.PersistedItemArray;
 import com.android.launcher3.util.Preconditions;
@@ -288,6 +288,7 @@
         }
 
         @Override
+        @Nullable
         public Intent getIntent() {
             return intent;
         }
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 87e8ebf..6c62b31 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -465,7 +465,7 @@
                 // occupied (if the feature is enabled) in order to account for the search
                 // container.
                 int spanX = mIDP.numSearchContainerColumns;
-                int spanY = FeatureFlags.EXPANDED_SMARTSPACE.get() ? 2 : 1;
+                int spanY = 1;
                 screen.markCells(0, 0, spanX, spanY, true);
             }
             occupied.put(item.screenId, screen);
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index f1c5d59..b644b6b 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -256,12 +256,10 @@
                     mApp.getModel()::onPackageIconsUpdated);
             logASplit(logger, "update icon cache");
 
-            if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
-                verifyNotStopped();
-                logASplit(logger, "save shortcuts in icon cache");
-                updateHandler.updateIcons(allShortcuts, new ShortcutCachingLogic(),
-                        mApp.getModel()::onPackageIconsUpdated);
-            }
+            verifyNotStopped();
+            logASplit(logger, "save shortcuts in icon cache");
+            updateHandler.updateIcons(allShortcuts, new ShortcutCachingLogic(),
+                    mApp.getModel()::onPackageIconsUpdated);
 
             // Take a break
             waitForIdle();
@@ -276,12 +274,10 @@
             mResults.bindDeepShortcuts();
             logASplit(logger, "bindDeepShortcuts");
 
-            if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
-                verifyNotStopped();
-                logASplit(logger, "save deep shortcuts in icon cache");
-                updateHandler.updateIcons(allDeepShortcuts,
-                        new ShortcutCachingLogic(), (pkgs, user) -> { });
-            }
+            verifyNotStopped();
+            logASplit(logger, "save deep shortcuts in icon cache");
+            updateHandler.updateIcons(allDeepShortcuts,
+                    new ShortcutCachingLogic(), (pkgs, user) -> { });
 
             // Take a break
             waitForIdle();
@@ -304,9 +300,7 @@
             logASplit(logger, "save widgets in icon cache");
 
             // fifth step
-            if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
-                loadFolderNames();
-            }
+            loadFolderNames();
 
             verifyNotStopped();
             updateHandler.finish();
diff --git a/src/com/android/launcher3/model/ModelUtils.java b/src/com/android/launcher3/model/ModelUtils.java
index 422af43..1ced0b1 100644
--- a/src/com/android/launcher3/model/ModelUtils.java
+++ b/src/com/android/launcher3/model/ModelUtils.java
@@ -29,7 +29,7 @@
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSet;
 
diff --git a/src/com/android/launcher3/model/PackageIncrementalDownloadUpdatedTask.java b/src/com/android/launcher3/model/PackageIncrementalDownloadUpdatedTask.java
index c0dc34a..b9fba9d 100644
--- a/src/com/android/launcher3/model/PackageIncrementalDownloadUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageIncrementalDownloadUpdatedTask.java
@@ -17,6 +17,8 @@
 
 import android.os.UserHandle;
 
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
@@ -31,19 +33,24 @@
  */
 public class PackageIncrementalDownloadUpdatedTask extends BaseModelUpdateTask {
 
+    @NonNull
     private final UserHandle mUser;
+
     private final int mProgress;
+
+    @NonNull
     private final String mPackageName;
 
-    public PackageIncrementalDownloadUpdatedTask(
-            String packageName, UserHandle user, float progress) {
+    public PackageIncrementalDownloadUpdatedTask(@NonNull final String packageName,
+            @NonNull final UserHandle user, final float progress) {
         mUser = user;
         mProgress = 1 - progress > 0.001 ? (int) (100 * progress) : 100;
         mPackageName = packageName;
     }
 
     @Override
-    public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList appsList) {
+    public void execute(@NonNull LauncherAppState app, @NonNull final BgDataModel dataModel,
+            @NonNull final AllAppsList appsList) {
         PackageInstallInfo downloadInfo = new PackageInstallInfo(
                 mPackageName,
                 PackageInstallInfo.STATUS_INSTALLED_DOWNLOADING,
diff --git a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
index b74d0fc..76a87ed 100644
--- a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
+++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
@@ -18,6 +18,8 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfo;
@@ -33,14 +35,16 @@
  */
 public class PackageInstallStateChangedTask extends BaseModelUpdateTask {
 
+    @NonNull
     private final PackageInstallInfo mInstallInfo;
 
-    public PackageInstallStateChangedTask(PackageInstallInfo installInfo) {
+    public PackageInstallStateChangedTask(@NonNull final PackageInstallInfo installInfo) {
         mInstallInfo = installInfo;
     }
 
     @Override
-    public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+    public void execute(@NonNull final LauncherAppState app, @NonNull final BgDataModel dataModel,
+            @NonNull final AllAppsList apps) {
         if (mInstallInfo.state == PackageInstallInfo.STATUS_INSTALLED) {
             try {
                 // For instant apps we do not get package-add. Use setting events to update
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 489bc38..3d9d81f 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -29,6 +29,8 @@
 import android.os.UserManager;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
@@ -57,7 +59,9 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Objects;
 import java.util.function.Predicate;
+import java.util.stream.Collectors;
 
 /**
  * Handles updates due to changes in package manager (app installed/updated/removed)
@@ -78,17 +82,23 @@
     public static final int OP_USER_AVAILABILITY_CHANGE = 7; // user available/unavailable
 
     private final int mOp;
+
+    @NonNull
     private final UserHandle mUser;
+
+    @NonNull
     private final String[] mPackages;
 
-    public PackageUpdatedTask(int op, UserHandle user, String... packages) {
+    public PackageUpdatedTask(final int op, @NonNull final UserHandle user,
+            @NonNull final String... packages) {
         mOp = op;
         mUser = user;
         mPackages = packages;
     }
 
     @Override
-    public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList appsList) {
+    public void execute(@NonNull final LauncherAppState app, @NonNull final BgDataModel dataModel,
+            @NonNull final AllAppsList appsList) {
         final Context context = app.getContext();
         final IconCache iconCache = app.getIconCache();
 
@@ -343,7 +353,12 @@
                     .or(ItemInfoMatcher.ofComponents(removedComponents, mUser))
                     .and(ItemInfoMatcher.ofItemIds(forceKeepShortcuts).negate());
             deleteAndBindComponentsRemoved(removeMatch,
-                    "removed because the corresponding package or component is removed");
+                    "removed because the corresponding package or component is removed. "
+                            + "mOp=" + mOp + " removedPackages=" + removedPackages.stream().collect(
+                                    Collectors.joining(",", "[", "]"))
+                            + " removedComponents=" + removedComponents.stream()
+                            .filter(Objects::nonNull).map(ComponentName::toShortString)
+                            .collect(Collectors.joining(",", "[", "]")));
 
             // Remove any queued items from the install queue
             ItemInstallQueue.INSTANCE.get(context)
diff --git a/src/com/android/launcher3/model/ReloadStringCacheTask.java b/src/com/android/launcher3/model/ReloadStringCacheTask.java
index f4d4298..34f7057 100644
--- a/src/com/android/launcher3/model/ReloadStringCacheTask.java
+++ b/src/com/android/launcher3/model/ReloadStringCacheTask.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.model;
 
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.LauncherAppState;
 
 /**
@@ -22,14 +24,17 @@
  * {@link android.app.admin.DevicePolicyManager#ACTION_DEVICE_POLICY_RESOURCE_UPDATED}.
  */
 public class ReloadStringCacheTask extends BaseModelUpdateTask {
+
+    @NonNull
     private ModelDelegate mModelDelegate;
 
-    public ReloadStringCacheTask(ModelDelegate modelDelegate) {
+    public ReloadStringCacheTask(@NonNull final ModelDelegate modelDelegate) {
         mModelDelegate = modelDelegate;
     }
 
     @Override
-    public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList appsList) {
+    public void execute(@NonNull final LauncherAppState app, @NonNull final BgDataModel dataModel,
+            @NonNull final AllAppsList appsList) {
         synchronized (dataModel) {
             mModelDelegate.loadStringCache(dataModel.stringCache);
             StringCache cloneSC = dataModel.stringCache.clone();
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
index 1026e0b..a6a04a7 100644
--- a/src/com/android/launcher3/model/ShortcutsChangedTask.java
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -19,6 +19,8 @@
 import android.content.pm.ShortcutInfo;
 import android.os.UserHandle;
 
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -38,13 +40,20 @@
  */
 public class ShortcutsChangedTask extends BaseModelUpdateTask {
 
+    @NonNull
     private final String mPackageName;
+
+    @NonNull
     private final List<ShortcutInfo> mShortcuts;
+
+    @NonNull
     private final UserHandle mUser;
+
     private final boolean mUpdateIdMap;
 
-    public ShortcutsChangedTask(String packageName, List<ShortcutInfo> shortcuts,
-            UserHandle user, boolean updateIdMap) {
+    public ShortcutsChangedTask(@NonNull final String packageName,
+            @NonNull final List<ShortcutInfo> shortcuts, @NonNull final UserHandle user,
+            final boolean updateIdMap) {
         mPackageName = packageName;
         mShortcuts = shortcuts;
         mUser = user;
@@ -52,7 +61,8 @@
     }
 
     @Override
-    public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+    public void execute(@NonNull final LauncherAppState app, @NonNull final BgDataModel dataModel,
+            @NonNull final AllAppsList apps) {
         final Context context = app.getContext();
         // Find WorkspaceItemInfo's that have changed on the workspace.
         ArrayList<WorkspaceItemInfo> matchingWorkspaceItems = new ArrayList<>();
diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java
index 1565b19..63ca35b 100644
--- a/src/com/android/launcher3/model/UserLockStateChangedTask.java
+++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java
@@ -21,6 +21,8 @@
 import android.content.pm.ShortcutInfo;
 import android.os.UserHandle;
 
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -40,16 +42,18 @@
  */
 public class UserLockStateChangedTask extends BaseModelUpdateTask {
 
+    @NonNull
     private final UserHandle mUser;
     private boolean mIsUserUnlocked;
 
-    public UserLockStateChangedTask(UserHandle user, boolean isUserUnlocked) {
+    public UserLockStateChangedTask(@NonNull final UserHandle user, final boolean isUserUnlocked) {
         mUser = user;
         mIsUserUnlocked = isUserUnlocked;
     }
 
     @Override
-    public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+    public void execute(@NonNull final LauncherAppState app, @NonNull final BgDataModel dataModel,
+            @NonNull final AllAppsList apps) {
         Context context = app.getContext();
 
         HashMap<ShortcutKey, ShortcutInfo> pinnedShortcuts = new HashMap<>();
diff --git a/src/com/android/launcher3/model/data/AppInfo.java b/src/com/android/launcher3/model/data/AppInfo.java
index 5b2bcf5..34972e7 100644
--- a/src/com/android/launcher3/model/data/AppInfo.java
+++ b/src/com/android/launcher3/model/data/AppInfo.java
@@ -55,6 +55,7 @@
      */
     public Intent intent;
 
+    @NonNull
     public ComponentName componentName;
 
     // Section name used for indexing.
@@ -65,6 +66,7 @@
     }
 
     @Override
+    @Nullable
     public Intent getIntent() {
         return intent;
     }
@@ -151,7 +153,7 @@
                         | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
     }
 
-    @Nullable
+    @NonNull
     @Override
     public ComponentName getTargetComponent() {
         return componentName;
diff --git a/src/com/android/launcher3/model/data/FolderInfo.java b/src/com/android/launcher3/model/data/FolderInfo.java
index efebce3..524b769 100644
--- a/src/com/android/launcher3/model/data/FolderInfo.java
+++ b/src/com/android/launcher3/model/data/FolderInfo.java
@@ -26,11 +26,11 @@
 
 import android.os.Process;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.folder.FolderNameInfos;
 import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.logger.LauncherAtom.Attribute;
@@ -155,7 +155,7 @@
     }
 
     @Override
-    public void onAddToDatabase(ContentWriter writer) {
+    public void onAddToDatabase(@NonNull ContentWriter writer) {
         super.onAddToDatabase(writer);
         writer.put(LauncherSettings.Favorites.TITLE, title)
                 .put(LauncherSettings.Favorites.OPTIONS, options);
@@ -207,8 +207,9 @@
         return String.format("%s; labelState=%s", super.dumpProperties(), getLabelState());
     }
 
+    @NonNull
     @Override
-    public LauncherAtom.ItemInfo buildProto(FolderInfo fInfo) {
+    public LauncherAtom.ItemInfo buildProto(@Nullable FolderInfo fInfo) {
         FolderIcon.Builder folderIcon = FolderIcon.newBuilder()
                 .setCardinality(contents.size());
         if (LabelState.SUGGESTED.equals(getLabelState())) {
@@ -262,6 +263,7 @@
                                 : LabelState.SUGGESTED;
     }
 
+    @NonNull
     @Override
     public ItemInfo makeShallowCopy() {
         FolderInfo folderInfo = new FolderInfo();
@@ -273,6 +275,7 @@
     /**
      * Returns {@link LauncherAtom.FolderIcon} wrapped as {@link LauncherAtom.ItemInfo} for logging.
      */
+    @NonNull
     @Override
     public LauncherAtom.ItemInfo buildProto() {
         return buildProto(null);
@@ -321,12 +324,6 @@
             return LauncherAtom.ToState.TO_STATE_UNSPECIFIED;
         }
 
-        if (!FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
-            return title.length() > 0
-                    ? LauncherAtom.ToState.TO_CUSTOM_WITH_SUGGESTIONS_DISABLED
-                    : LauncherAtom.ToState.TO_EMPTY_WITH_SUGGESTIONS_DISABLED;
-        }
-
         // TODO: if suggestedFolderNames is null then it infrastructure issue, not
         // ranking issue. We should log these appropriately.
         if (suggestedFolderNames == null || !suggestedFolderNames.hasSuggestions()) {
diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java
index 1e8e3ca..466f63f 100644
--- a/src/com/android/launcher3/model/data/ItemInfo.java
+++ b/src/com/android/launcher3/model/data/ItemInfo.java
@@ -42,6 +42,7 @@
 import android.os.Process;
 import android.os.UserHandle;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.LauncherSettings;
@@ -141,30 +142,34 @@
     /**
      * Title of the item
      */
+    @Nullable
     public CharSequence title;
 
     /**
      * Content description of the item.
      */
+    @Nullable
     public CharSequence contentDescription;
 
     /**
      * When the instance is created using {@link #copyFrom}, this field is used to keep track of
      * original {@link ComponentName}.
      */
+    @Nullable
     private ComponentName mComponentName;
 
+    @NonNull
     public UserHandle user;
 
     public ItemInfo() {
         user = Process.myUserHandle();
     }
 
-    protected ItemInfo(ItemInfo info) {
+    protected ItemInfo(@NonNull final ItemInfo info) {
         copyFrom(info);
     }
 
-    public void copyFrom(ItemInfo info) {
+    public void copyFrom(@NonNull final ItemInfo info) {
         id = info.id;
         title = info.title;
         cellX = info.cellX;
@@ -182,6 +187,7 @@
         mComponentName = info.getTargetComponent();
     }
 
+    @Nullable
     public Intent getIntent() {
         return null;
     }
@@ -209,7 +215,7 @@
                         : null;
     }
 
-    public void writeToValues(ContentWriter writer) {
+    public void writeToValues(@NonNull final ContentWriter writer) {
         writer.put(LauncherSettings.Favorites.ITEM_TYPE, itemType)
                 .put(LauncherSettings.Favorites.CONTAINER, container)
                 .put(LauncherSettings.Favorites.SCREEN, screenId)
@@ -220,7 +226,7 @@
                 .put(LauncherSettings.Favorites.RANK, rank);
     }
 
-    public void readFromValues(ContentValues values) {
+    public void readFromValues(@NonNull final ContentValues values) {
         itemType = values.getAsInteger(LauncherSettings.Favorites.ITEM_TYPE);
         container = values.getAsInteger(LauncherSettings.Favorites.CONTAINER);
         screenId = values.getAsInteger(LauncherSettings.Favorites.SCREEN);
@@ -234,7 +240,7 @@
     /**
      * Write the fields of this item to the DB
      */
-    public void onAddToDatabase(ContentWriter writer) {
+    public void onAddToDatabase(@NonNull final ContentWriter writer) {
         if (Workspace.EXTRA_EMPTY_SCREEN_IDS.contains(screenId)) {
             // We should never persist an item on the extra empty screen.
             throw new RuntimeException("Screen id should not be extra empty screen: " + screenId);
@@ -245,10 +251,12 @@
     }
 
     @Override
+    @NonNull
     public final String toString() {
         return getClass().getSimpleName() + "(" + dumpProperties() + ")";
     }
 
+    @NonNull
     protected String dumpProperties() {
         return "id=" + id
                 + " type=" + LauncherSettings.Favorites.itemTypeToString(itemType)
@@ -288,14 +296,17 @@
     /**
      * Creates {@link LauncherAtom.ItemInfo} with important fields and parent container info.
      */
+    @NonNull
     public LauncherAtom.ItemInfo buildProto() {
         return buildProto(null);
     }
 
     /**
      * Creates {@link LauncherAtom.ItemInfo} with important fields and parent container info.
+     * @param fInfo
      */
-    public LauncherAtom.ItemInfo buildProto(FolderInfo fInfo) {
+    @NonNull
+    public LauncherAtom.ItemInfo buildProto(@Nullable final FolderInfo fInfo) {
         LauncherAtom.ItemInfo.Builder itemBuilder = getDefaultItemInfoBuilder();
         Optional<ComponentName> nullableComponent = Optional.ofNullable(getTargetComponent());
         switch (itemType) {
@@ -373,6 +384,7 @@
         return itemBuilder.build();
     }
 
+    @NonNull
     protected LauncherAtom.ItemInfo.Builder getDefaultItemInfoBuilder() {
         LauncherAtom.ItemInfo.Builder itemBuilder = LauncherAtom.ItemInfo.newBuilder();
         itemBuilder.setIsWork(!Process.myUserHandle().equals(user));
@@ -383,6 +395,7 @@
     /**
      * Returns {@link ContainerInfo} used when logging this item.
      */
+    @NonNull
     public ContainerInfo getContainerInfo() {
         switch (container) {
             case CONTAINER_HOTSEAT:
@@ -447,6 +460,7 @@
      * Returns non-AOSP container wrapped by {@link ExtendedContainers} object. Should be overridden
      * by build variants.
      */
+    @NonNull
     protected ExtendedContainers getExtendedContainer() {
         return ExtendedContainers.getDefaultInstance();
     }
@@ -454,6 +468,7 @@
     /**
      * Returns shallow copy of the object.
      */
+    @NonNull
     public ItemInfo makeShallowCopy() {
         ItemInfo itemInfo = new ItemInfo();
         itemInfo.copyFrom(this);
@@ -463,7 +478,8 @@
     /**
      * Sets the title of the item and writes to DB model if needed.
      */
-    public void setTitle(CharSequence title, ModelWriter modelWriter) {
+    public void setTitle(@Nullable final CharSequence title,
+            @Nullable final ModelWriter modelWriter) {
         this.title = title;
     }
 }
diff --git a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
index 76a0c4d..e5fb015 100644
--- a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
@@ -16,7 +16,6 @@
 
 package com.android.launcher3.model.data;
 
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 
@@ -220,10 +219,10 @@
     /** Creates an intent to that launches the app store at this app's page. */
     @Nullable
     public Intent getMarketIntent(Context context) {
-        ComponentName componentName = getTargetComponent();
+        String targetPackage = getTargetPackage();
 
-        return componentName != null
-                ? new PackageManagerHelper(context).getMarketIntent(componentName.getPackageName())
+        return targetPackage != null
+                ? new PackageManagerHelper(context).getMarketIntent(targetPackage)
                 : null;
     }
 
diff --git a/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java b/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
index e57a895..1fbe04f 100644
--- a/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
+++ b/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
@@ -29,6 +29,7 @@
 import android.content.res.Resources;
 import android.os.Process;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.Launcher;
@@ -191,7 +192,7 @@
     }
 
     @Override
-    public void onAddToDatabase(ContentWriter writer) {
+    public void onAddToDatabase(@NonNull ContentWriter writer) {
         super.onAddToDatabase(writer);
         writer.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId)
                 .put(LauncherSettings.Favorites.APPWIDGET_PROVIDER, providerName.flattenToString())
@@ -283,8 +284,9 @@
         }
     }
 
+    @NonNull
     @Override
-    public LauncherAtom.ItemInfo buildProto(FolderInfo folderInfo) {
+    public LauncherAtom.ItemInfo buildProto(@Nullable FolderInfo folderInfo) {
         LauncherAtom.ItemInfo info = super.buildProto(folderInfo);
         return info.toBuilder()
                 .setWidget(info.getWidget().toBuilder().setWidgetFeatures(widgetFeatures))
diff --git a/src/com/android/launcher3/model/data/SearchActionItemInfo.java b/src/com/android/launcher3/model/data/SearchActionItemInfo.java
index e879313..04042ea 100644
--- a/src/com/android/launcher3/model/data/SearchActionItemInfo.java
+++ b/src/com/android/launcher3/model/data/SearchActionItemInfo.java
@@ -24,6 +24,7 @@
 import android.os.Process;
 import android.os.UserHandle;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.LauncherAppState;
@@ -70,7 +71,7 @@
     }
 
     @Override
-    public void copyFrom(com.android.launcher3.model.data.ItemInfo info) {
+    public void copyFrom(@NonNull com.android.launcher3.model.data.ItemInfo info) {
         super.copyFrom(info);
         SearchActionItemInfo itemInfo = (SearchActionItemInfo) info;
         this.mFallbackPackageName = itemInfo.mFallbackPackageName;
@@ -91,6 +92,7 @@
     }
 
     @Override
+    @Nullable
     public Intent getIntent() {
         return mIntent;
     }
@@ -131,8 +133,9 @@
         return new SearchActionItemInfo(this);
     }
 
+    @NonNull
     @Override
-    public ItemInfo buildProto(FolderInfo fInfo) {
+    public ItemInfo buildProto(@Nullable FolderInfo fInfo) {
         SearchActionItem.Builder itemBuilder = SearchActionItem.newBuilder()
                 .setPackageName(mFallbackPackageName);
 
diff --git a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
index 2b3da33..59ef320 100644
--- a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
+++ b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
@@ -75,6 +75,7 @@
     /**
      * The intent used to start the application.
      */
+    @NonNull
     public Intent intent;
 
     /**
@@ -130,7 +131,7 @@
     }
 
     @Override
-    public void onAddToDatabase(ContentWriter writer) {
+    public void onAddToDatabase(@NonNull ContentWriter writer) {
         super.onAddToDatabase(writer);
         writer.put(Favorites.TITLE, title)
                 .put(Favorites.INTENT, getIntent())
@@ -147,6 +148,7 @@
     }
 
     @Override
+    @NonNull
     public Intent getIntent() {
         return intent;
     }
@@ -164,7 +166,8 @@
         return isPromise() && !hasStatusFlag(FLAG_SUPPORTS_WEB_UI);
     }
 
-    public void updateFromDeepShortcutInfo(ShortcutInfo shortcutInfo, Context context) {
+    public void updateFromDeepShortcutInfo(@NonNull final ShortcutInfo shortcutInfo,
+            @NonNull final Context context) {
         // {@link ShortcutInfo#getActivity} can change during an update. Recreate the intent
         intent = ShortcutKey.makeIntent(shortcutInfo);
         title = shortcutInfo.getShortLabel();
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
index 29eefe2..439e1c7 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.pageindicators;
 
+import static com.android.launcher3.config.FeatureFlags.SHOW_DELIGHTFUL_PAGINATION_FOLDER;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
@@ -37,6 +39,7 @@
 
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.util.Themes;
 
 /**
@@ -55,10 +58,14 @@
 
     private static final int DOT_ACTIVE_ALPHA = 255;
     private static final int DOT_INACTIVE_ALPHA = 128;
+    private static final int DOT_GAP_FACTOR = 3;
+    private static final float DOT_GAP_FACTOR_FLOAT = 3.8f;
 
     // This value approximately overshoots to 1.5 times the original size.
     private static final float ENTER_ANIMATION_OVERSHOOT_TENSION = 4.9f;
 
+    private static final float INDICATOR_ROTATION = 180f;
+
     private static final RectF sTempRect = new RectF();
 
     private static final Property<PageIndicatorDots, Float> CURRENT_POSITION
@@ -76,21 +83,25 @@
         }
     };
 
-    private final Paint mCirclePaint;
+    private final Paint mPaginationPaint;
     private final float mDotRadius;
+    private final float mCircleGap;
+    private final float mPageIndicatorSize;
     private final boolean mIsRtl;
 
     private int mNumPages;
     private int mActivePage;
+    private int mCurrentScroll;
+    private int mTotalScroll;
 
     /**
      * The current position of the active dot including the animation progress.
      * For ex:
-     *   0.0  => Active dot is at position 0
-     *   0.33 => Active dot is at position 0 and is moving towards 1
-     *   0.50 => Active dot is at position [0, 1]
-     *   0.77 => Active dot has left position 0 and is collapsing towards position 1
-     *   1.0  => Active dot is at position 1
+     * 0.0  => Active dot is at position 0
+     * 0.33 => Active dot is at position 0 and is moving towards 1
+     * 0.50 => Active dot is at position [0, 1]
+     * 0.77 => Active dot has left position 0 and is collapsing towards position 1
+     * 1.0  => Active dot is at position 1
      */
     private float mCurrentPosition;
     private float mFinalPosition;
@@ -109,37 +120,56 @@
     public PageIndicatorDots(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
 
-        mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-        mCirclePaint.setStyle(Style.FILL);
-        mCirclePaint.setColor(Themes.getAttrColor(context, R.attr.folderPaginationColor));
+        mPaginationPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        mPaginationPaint.setStyle(Style.FILL);
+        mPaginationPaint.setColor(Themes.getAttrColor(context, R.attr.folderPaginationColor));
         mDotRadius = getResources().getDimension(R.dimen.page_indicator_dot_size) / 2;
-        setOutlineProvider(new MyOutlineProver());
 
+        if (SHOW_DELIGHTFUL_PAGINATION_FOLDER.get()) {
+            mCircleGap = DOT_GAP_FACTOR_FLOAT * mDotRadius;
+        } else {
+            mCircleGap = DOT_GAP_FACTOR * mDotRadius;
+        }
+        mPageIndicatorSize = getResources().getDimension(
+                R.dimen.page_indicator_current_page_indicator_size);
+        if (!SHOW_DELIGHTFUL_PAGINATION_FOLDER.get()) {
+            setOutlineProvider(new MyOutlineProver());
+        }
         mIsRtl = Utilities.isRtl(getResources());
     }
 
     @Override
     public void setScroll(int currentScroll, int totalScroll) {
-        if (mNumPages > 1) {
-            if (mIsRtl) {
-                currentScroll = totalScroll - currentScroll;
-            }
-            int scrollPerPage = totalScroll / (mNumPages - 1);
-            int pageToLeft = currentScroll / scrollPerPage;
-            int pageToLeftScroll = pageToLeft * scrollPerPage;
-            int pageToRightScroll = pageToLeftScroll + scrollPerPage;
+        if (mNumPages <= 1) {
+            return;
+        }
 
-            float scrollThreshold = SHIFT_THRESHOLD * scrollPerPage;
-            if (currentScroll < pageToLeftScroll + scrollThreshold) {
-                // scroll is within the left page's threshold
-                animateToPosition(pageToLeft);
-            } else if (currentScroll > pageToRightScroll - scrollThreshold) {
-                // scroll is far enough from left page to go to the right page
-                animateToPosition(pageToLeft + 1);
-            } else {
-                // scroll is between left and right page
-                animateToPosition(pageToLeft + SHIFT_PER_ANIMATION);
-            }
+        if (mIsRtl) {
+            currentScroll = totalScroll - currentScroll;
+        }
+
+        if (SHOW_DELIGHTFUL_PAGINATION_FOLDER.get()) {
+            mCurrentScroll = currentScroll;
+            mTotalScroll = totalScroll;
+            invalidate();
+            return;
+        }
+
+        int scrollPerPage = totalScroll / (mNumPages - 1);
+        int pageToLeft = currentScroll / scrollPerPage;
+        int pageToLeftScroll = pageToLeft * scrollPerPage;
+        int pageToRightScroll = pageToLeftScroll + scrollPerPage;
+
+        float scrollThreshold = SHIFT_THRESHOLD * scrollPerPage;
+        if (currentScroll < pageToLeftScroll + scrollThreshold) {
+            // scroll is within the left page's threshold
+            animateToPosition(pageToLeft);
+        } else if (currentScroll > pageToRightScroll - scrollThreshold) {
+            // scroll is far enough from left page to go to the right page
+            animateToPosition(pageToLeft + 1);
+        } else {
+            // scroll is between left and right page
+            animateToPosition(pageToLeft + SHIFT_PER_ANIMATION);
         }
     }
 
@@ -177,7 +207,7 @@
     }
 
     public void playEntryAnimation() {
-        int count  = mEntryAnimationRadiusFactors.length;
+        int count = mEntryAnimationRadiusFactors.length;
         if (count == 0) {
             mEntryAnimationRadiusFactors = null;
             invalidate();
@@ -231,16 +261,16 @@
         // Add extra spacing of mDotRadius on all sides so than entry animation could be run.
         int width = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY ?
                 MeasureSpec.getSize(widthMeasureSpec) : (int) ((mNumPages * 3 + 2) * mDotRadius);
-        int height= MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY ?
-                MeasureSpec.getSize(heightMeasureSpec) : (int) (4 * mDotRadius);
+        int height = MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY
+                ? MeasureSpec.getSize(heightMeasureSpec) : (int) (4 * mDotRadius);
         setMeasuredDimension(width, height);
     }
 
     @Override
     protected void onDraw(Canvas canvas) {
         // Draw all page indicators;
-        float circleGap = 3 * mDotRadius;
-        float startX = (getWidth() - mNumPages * circleGap + mDotRadius) / 2;
+        float circleGap = mCircleGap;
+        float startX = (getWidth() - (mNumPages * circleGap) + mDotRadius) / 2;
 
         float x = startX + mDotRadius;
         float y = getHeight() / 2;
@@ -252,43 +282,98 @@
                 circleGap = -circleGap;
             }
             for (int i = 0; i < mEntryAnimationRadiusFactors.length; i++) {
-                mCirclePaint.setAlpha(i == mActivePage ? DOT_ACTIVE_ALPHA : DOT_INACTIVE_ALPHA);
-                canvas.drawCircle(x, y, mDotRadius * mEntryAnimationRadiusFactors[i], mCirclePaint);
+                mPaginationPaint.setAlpha(i == mActivePage ? DOT_ACTIVE_ALPHA : DOT_INACTIVE_ALPHA);
+                if (SHOW_DELIGHTFUL_PAGINATION_FOLDER.get()) {
+                    canvas.drawCircle(x, y, getRadius(x) * mEntryAnimationRadiusFactors[i],
+                            mPaginationPaint);
+                } else {
+                    canvas.drawCircle(x, y, mDotRadius * mEntryAnimationRadiusFactors[i],
+                            mPaginationPaint);
+                }
                 x += circleGap;
             }
         } else {
-            mCirclePaint.setAlpha(DOT_INACTIVE_ALPHA);
+            mPaginationPaint.setAlpha(DOT_INACTIVE_ALPHA);
             for (int i = 0; i < mNumPages; i++) {
-                canvas.drawCircle(x, y, mDotRadius, mCirclePaint);
+                if (SHOW_DELIGHTFUL_PAGINATION_FOLDER.get()) {
+                    canvas.drawCircle(x, y, getRadius(x), mPaginationPaint);
+                } else {
+                    canvas.drawCircle(x, y, mDotRadius, mPaginationPaint);
+                }
                 x += circleGap;
             }
 
-            mCirclePaint.setAlpha(DOT_ACTIVE_ALPHA);
-            canvas.drawRoundRect(getActiveRect(), mDotRadius, mDotRadius, mCirclePaint);
+            mPaginationPaint.setAlpha(DOT_ACTIVE_ALPHA);
+
+            if (SHOW_DELIGHTFUL_PAGINATION_FOLDER.get()) {
+                RectF currRect = getActiveRect();
+                int scrollPerPage = getScrollPerPage();
+
+                // This IF is to avoid division by 0
+                if (scrollPerPage != 0) {
+                    int delta = mCurrentScroll % scrollPerPage;
+                    canvas.rotate((INDICATOR_ROTATION * delta) / scrollPerPage,
+                            currRect.centerX(), currRect.centerY());
+                }
+
+                canvas.drawRect(currRect, mPaginationPaint);
+            } else {
+                canvas.drawRoundRect(getActiveRect(), mDotRadius, mDotRadius, mPaginationPaint);
+            }
         }
     }
 
+    /**
+     * Returns the radius of the circle based on how close the page indicator is to it
+     *
+     * @param dotPositionX is the position the dot is located at in the x-axis
+     */
+    private float getRadius(float dotPositionX) {
+
+        float startXIndicator =
+                ((getWidth() - (mNumPages * mCircleGap) + mDotRadius) / 2) - getOffset();
+        float indicatorPosition = startXIndicator + getIndicatorScrollDistance()
+                + (mPageIndicatorSize / 2);
+
+        // If the indicator gets close enough to a dot then we change the radius
+        // of the dot based on how close the indicator is to it.
+        float dotDistance = Math.abs(indicatorPosition - dotPositionX);
+        if (dotDistance <= mCircleGap) {
+            return Utilities.mapToRange(dotDistance, 0, mCircleGap, 0f, mDotRadius,
+                    Interpolators.LINEAR);
+        }
+        return mDotRadius;
+    }
+
     private RectF getActiveRect() {
         float startCircle = (int) mCurrentPosition;
         float delta = mCurrentPosition - startCircle;
         float diameter = 2 * mDotRadius;
-        float circleGap = 3 * mDotRadius;
-        float startX = (getWidth() - mNumPages * circleGap + mDotRadius) / 2;
+        float startX;
 
-        sTempRect.top = getHeight() * 0.5f - mDotRadius;
-        sTempRect.bottom = getHeight() * 0.5f + mDotRadius;
-        sTempRect.left = startX + startCircle * circleGap;
-        sTempRect.right = sTempRect.left + diameter;
-
-        if (delta < SHIFT_PER_ANIMATION) {
-            // dot is capturing the right circle.
-            sTempRect.right += delta * circleGap * 2;
+        if (SHOW_DELIGHTFUL_PAGINATION_FOLDER.get()) {
+            startX = ((getWidth() - (mNumPages * mCircleGap) + mDotRadius) / 2) - getOffset();
+            sTempRect.top = (getHeight() - mPageIndicatorSize) * 0.5f;
+            sTempRect.bottom = (getHeight() + mPageIndicatorSize) * 0.5f;
+            sTempRect.left = startX + getIndicatorScrollDistance();
+            sTempRect.right = sTempRect.left + mPageIndicatorSize;
         } else {
-            // Dot is leaving the left circle.
-            sTempRect.right += circleGap;
+            startX = ((getWidth() - (mNumPages * mCircleGap) + mDotRadius) / 2);
+            sTempRect.top = (getHeight() * 0.5f) - mDotRadius;
+            sTempRect.bottom = (getHeight() * 0.5f) + mDotRadius;
+            sTempRect.left = startX + (startCircle * mCircleGap);
+            sTempRect.right = sTempRect.left + diameter;
 
-            delta -= SHIFT_PER_ANIMATION;
-            sTempRect.left += delta * circleGap * 2;
+            if (delta < SHIFT_PER_ANIMATION) {
+                // dot is capturing the right circle.
+                sTempRect.right += delta * mCircleGap * 2;
+            } else {
+                // Dot is leaving the left circle.
+                sTempRect.right += mCircleGap;
+
+                delta -= SHIFT_PER_ANIMATION;
+                sTempRect.left += delta * mCircleGap * 2;
+            }
         }
 
         if (mIsRtl) {
@@ -296,9 +381,33 @@
             sTempRect.right = getWidth() - sTempRect.left;
             sTempRect.left = sTempRect.right - rectWidth;
         }
+
         return sTempRect;
     }
 
+    /**
+     * The offset between the radius of the dot and the midpoint of the indicator so that
+     * the indicator is centered in with the indicator circles
+     */
+    private float getOffset() {
+        return (mPageIndicatorSize / 2) - mDotRadius;
+    }
+
+    /**
+     * Returns an int that is the amount we need to scroll per page
+     */
+    private int getScrollPerPage() {
+        return mNumPages > 1 ? mTotalScroll / (mNumPages - 1) : 0;
+    }
+
+    /**
+     * The current scroll adjusted for the distance the indicator needs to travel on the screen
+     */
+    private float getIndicatorScrollDistance() {
+        int scrollPerPage = getScrollPerPage();
+        return scrollPerPage != 0 ? ((float) mCurrentScroll / scrollPerPage) * mCircleGap : 0;
+    }
+
     private class MyOutlineProver extends ViewOutlineProvider {
 
         @Override
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/pm/InstallSessionHelper.java b/src/com/android/launcher3/pm/InstallSessionHelper.java
index 618f926..16bb868 100644
--- a/src/com/android/launcher3/pm/InstallSessionHelper.java
+++ b/src/com/android/launcher3/pm/InstallSessionHelper.java
@@ -30,6 +30,7 @@
 import android.util.Log;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.WorkerThread;
 
@@ -39,7 +40,7 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.model.ItemInstallQueue;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.MainThreadInitializedObject;
@@ -51,38 +52,50 @@
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * Utility class to tracking install sessions
  */
 public class InstallSessionHelper {
 
+    @NonNull
     private static final String LOG = "InstallSessionHelper";
 
     // Set<String> of session ids of promise icons that have been added to the home screen
     // as FLAG_PROMISE_NEW_INSTALLS.
+    @NonNull
     protected static final String PROMISE_ICON_IDS = "promise_icon_ids";
 
     private static final boolean DEBUG = false;
 
+    @NonNull
     public static final MainThreadInitializedObject<InstallSessionHelper> INSTANCE =
             new MainThreadInitializedObject<>(InstallSessionHelper::new);
 
+    @Nullable
     private final LauncherApps mLauncherApps;
+
+    @NonNull
     private final Context mAppContext;
 
+    @NonNull
     private final PackageInstaller mInstaller;
+
+    @NonNull
     private final HashMap<String, Boolean> mSessionVerifiedMap = new HashMap<>();
 
+    @Nullable
     private IntSet mPromiseIconIds;
 
-    public InstallSessionHelper(Context context) {
+    public InstallSessionHelper(@NonNull final Context context) {
         mInstaller = context.getPackageManager().getPackageInstaller();
         mAppContext = context.getApplicationContext();
         mLauncherApps = context.getSystemService(LauncherApps.class);
     }
 
     @WorkerThread
+    @NonNull
     private IntSet getPromiseIconIds() {
         Preconditions.assertWorkerThread();
         if (mPromiseIconIds != null) {
@@ -108,6 +121,7 @@
         return mPromiseIconIds;
     }
 
+    @NonNull
     public HashMap<PackageUserKey, SessionInfo> getActiveSessions() {
         HashMap<PackageUserKey, SessionInfo> activePackages = new HashMap<>();
         for (SessionInfo info : getAllVerifiedSessions()) {
@@ -117,6 +131,7 @@
         return activePackages;
     }
 
+    @Nullable
     public SessionInfo getActiveSessionInfo(UserHandle user, String pkg) {
         for (SessionInfo info : getAllVerifiedSessions()) {
             boolean match = pkg.equals(info.getAppPackageName());
@@ -136,11 +151,13 @@
                 .apply();
     }
 
-    SessionInfo getVerifiedSessionInfo(int sessionId) {
+    @Nullable
+    SessionInfo getVerifiedSessionInfo(final int sessionId) {
         return verify(mInstaller.getSessionInfo(sessionId));
     }
 
-    private SessionInfo verify(SessionInfo sessionInfo) {
+    @Nullable
+    private SessionInfo verify(@Nullable final SessionInfo sessionInfo) {
         if (sessionInfo == null
                 || sessionInfo.getInstallerPackageName() == null
                 || TextUtils.isEmpty(sessionInfo.getAppPackageName())) {
@@ -167,9 +184,10 @@
         return mSessionVerifiedMap.get(pkg) ? sessionInfo : null;
     }
 
+    @NonNull
     public List<SessionInfo> getAllVerifiedSessions() {
         List<SessionInfo> list = new ArrayList<>(Utilities.ATLEAST_Q
-                ? mLauncherApps.getAllPackageInstallerSessions()
+                ? Objects.requireNonNull(mLauncherApps).getAllPackageInstallerSessions()
                 : mInstaller.getAllSessions());
         Iterator<SessionInfo> it = list.iterator();
         while (it.hasNext()) {
@@ -201,12 +219,12 @@
     }
 
     @WorkerThread
-    public boolean promiseIconAddedForId(int sessionId) {
+    public boolean promiseIconAddedForId(final int sessionId) {
         return getPromiseIconIds().contains(sessionId);
     }
 
     @WorkerThread
-    public void removePromiseIconId(int sessionId) {
+    public void removePromiseIconId(final int sessionId) {
         if (promiseIconAddedForId(sessionId)) {
             getPromiseIconIds().getArray().removeValue(sessionId);
             updatePromiseIconPrefs();
@@ -222,17 +240,15 @@
      * - A promise icon for the session has not already been created
      */
     @WorkerThread
-    void tryQueuePromiseAppIcon(PackageInstaller.SessionInfo sessionInfo) {
+    void tryQueuePromiseAppIcon(@Nullable final PackageInstaller.SessionInfo sessionInfo) {
         if (TestProtocol.sDebugTracing) {
             Log.d(TestProtocol.MISSING_PROMISE_ICON, LOG + " tryQueuePromiseAppIcon"
-                    + ", FeatureFlags=" + FeatureFlags.PROMISE_APPS_NEW_INSTALLS.get()
                     + ", SessionCommitReceiveEnabled" + SessionCommitReceiver.isEnabled(mAppContext)
                     + ", verifySessionInfo(sessionInfo)=" + verifySessionInfo(sessionInfo)
                     + ", !promiseIconAdded=" + (sessionInfo != null
                     && !promiseIconAddedForId(sessionInfo.getSessionId())));
         }
-        if (FeatureFlags.PROMISE_APPS_NEW_INSTALLS.get()
-                && SessionCommitReceiver.isEnabled(mAppContext)
+        if (SessionCommitReceiver.isEnabled(mAppContext)
                 && verifySessionInfo(sessionInfo)
                 && !promiseIconAddedForId(sessionInfo.getSessionId())) {
             FileLog.d(LOG, "Adding package name to install queue: "
@@ -246,7 +262,7 @@
         }
     }
 
-    public boolean verifySessionInfo(PackageInstaller.SessionInfo sessionInfo) {
+    public boolean verifySessionInfo(@Nullable final PackageInstaller.SessionInfo sessionInfo) {
         if (TestProtocol.sDebugTracing) {
             boolean appNotInstalled = sessionInfo == null
                     || !new PackageManagerHelper(mAppContext)
@@ -269,14 +285,15 @@
                         sessionInfo.getAppPackageName(), getUserHandle(sessionInfo));
     }
 
-    public InstallSessionTracker registerInstallTracker(InstallSessionTracker.Callback callback) {
+    public InstallSessionTracker registerInstallTracker(
+            @Nullable final InstallSessionTracker.Callback callback) {
         InstallSessionTracker tracker = new InstallSessionTracker(
                 this, callback, mInstaller, mLauncherApps);
         tracker.register();
         return tracker;
     }
 
-    public static UserHandle getUserHandle(SessionInfo info) {
+    public static UserHandle getUserHandle(@NonNull final SessionInfo info) {
         return Utilities.ATLEAST_Q ? info.getUser() : Process.myUserHandle();
     }
 }
diff --git a/src/com/android/launcher3/pm/InstallSessionTracker.java b/src/com/android/launcher3/pm/InstallSessionTracker.java
index 75cf7a8..aeaa320 100644
--- a/src/com/android/launcher3/pm/InstallSessionTracker.java
+++ b/src/com/android/launcher3/pm/InstallSessionTracker.java
@@ -28,12 +28,15 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
 
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.util.PackageUserKey;
 
 import java.lang.ref.WeakReference;
+import java.util.Objects;
 
 @WorkerThread
 public class InstallSessionTracker extends PackageInstaller.SessionCallback {
@@ -41,14 +44,22 @@
     // Lazily initialized
     private SparseArray<PackageUserKey> mActiveSessions = null;
 
+    @NonNull
     private final WeakReference<InstallSessionHelper> mWeakHelper;
+
+    @NonNull
     private final WeakReference<Callback> mWeakCallback;
+
+    @NonNull
     private final PackageInstaller mInstaller;
+
+    @Nullable
     private final LauncherApps mLauncherApps;
 
 
-    InstallSessionTracker(InstallSessionHelper installerCompat, Callback callback,
-            PackageInstaller installer, LauncherApps launcherApps) {
+    InstallSessionTracker(@Nullable final InstallSessionHelper installerCompat,
+            @Nullable final Callback callback, @NonNull final PackageInstaller installer,
+            @Nullable LauncherApps launcherApps) {
         mWeakHelper = new WeakReference<>(installerCompat);
         mWeakCallback = new WeakReference<>(callback);
         mInstaller = installer;
@@ -56,7 +67,7 @@
     }
 
     @Override
-    public void onCreated(int sessionId) {
+    public void onCreated(final int sessionId) {
         InstallSessionHelper helper = mWeakHelper.get();
         Callback callback = mWeakCallback.get();
         if (TestProtocol.sDebugTracing) {
@@ -80,7 +91,7 @@
     }
 
     @Override
-    public void onFinished(int sessionId, boolean success) {
+    public void onFinished(final int sessionId, final boolean success) {
         InstallSessionHelper helper = mWeakHelper.get();
         Callback callback = mWeakCallback.get();
         if (callback == null || helper == null) {
@@ -108,7 +119,7 @@
     }
 
     @Override
-    public void onProgressChanged(int sessionId, float progress) {
+    public void onProgressChanged(final int sessionId, final float progress) {
         InstallSessionHelper helper = mWeakHelper.get();
         Callback callback = mWeakCallback.get();
         if (callback == null || helper == null) {
@@ -121,10 +132,10 @@
     }
 
     @Override
-    public void onActiveChanged(int sessionId, boolean active) { }
+    public void onActiveChanged(final int sessionId, final boolean active) { }
 
     @Override
-    public void onBadgingChanged(int sessionId) {
+    public void onBadgingChanged(final int sessionId) {
         InstallSessionHelper helper = mWeakHelper.get();
         Callback callback = mWeakCallback.get();
         if (callback == null || helper == null) {
@@ -136,8 +147,9 @@
         }
     }
 
-    private SessionInfo pushSessionDisplayToLauncher(
-            int sessionId, InstallSessionHelper helper, Callback callback) {
+    @Nullable
+    private SessionInfo pushSessionDisplayToLauncher(final int sessionId,
+            @NonNull final InstallSessionHelper helper, @NonNull final Callback callback) {
         SessionInfo session = helper.getVerifiedSessionInfo(sessionId);
         if (session != null && session.getAppPackageName() != null) {
             PackageUserKey key =
@@ -149,7 +161,9 @@
         return null;
     }
 
-    private SparseArray<PackageUserKey> getActiveSessionMap(InstallSessionHelper helper) {
+    @NonNull
+    private SparseArray<PackageUserKey> getActiveSessionMap(
+            @NonNull final InstallSessionHelper helper) {
         if (mActiveSessions == null) {
             mActiveSessions = new SparseArray<>();
             helper.getActiveSessions().forEach(
@@ -162,7 +176,8 @@
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
             mInstaller.registerSessionCallback(this, MODEL_EXECUTOR.getHandler());
         } else {
-            mLauncherApps.registerPackageInstallerSessionCallback(MODEL_EXECUTOR, this);
+            Objects.requireNonNull(mLauncherApps).registerPackageInstallerSessionCallback(
+                    MODEL_EXECUTOR, this);
         }
     }
 
@@ -170,18 +185,18 @@
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
             mInstaller.unregisterSessionCallback(this);
         } else {
-            mLauncherApps.unregisterPackageInstallerSessionCallback(this);
+            Objects.requireNonNull(mLauncherApps).unregisterPackageInstallerSessionCallback(this);
         }
     }
 
     public interface Callback {
 
-        void onSessionFailure(String packageName, UserHandle user);
+        void onSessionFailure(@NonNull String packageName, @NonNull UserHandle user);
 
-        void onUpdateSessionDisplay(PackageUserKey key, SessionInfo info);
+        void onUpdateSessionDisplay(@NonNull PackageUserKey key, @NonNull SessionInfo info);
 
-        void onPackageStateChanged(PackageInstallInfo info);
+        void onPackageStateChanged(@NonNull PackageInstallInfo info);
 
-        void onInstallSessionCreated(PackageInstallInfo info);
+        void onInstallSessionCreated(@NonNull PackageInstallInfo info);
     }
 }
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/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
index a2ab7f9..a55f7e3 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -29,7 +29,9 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.ActivityAllAppsContainerView;
 import com.android.launcher3.model.BgDataModel;
 import com.android.launcher3.model.StringCache;
@@ -39,6 +41,8 @@
 import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.popup.PopupDataProvider;
 import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.IntSet;
+import com.android.launcher3.util.OnboardingPrefs;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.BaseDragLayer;
 
@@ -61,11 +65,16 @@
     private boolean mAppDrawerShown = false;
 
     private StringCache mStringCache;
+    private OnboardingPrefs<?> mOnboardingPrefs;
+    private boolean mBindingItems = false;
+    private SecondaryDisplayPredictions mSecondaryDisplayPredictions;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         mModel = LauncherAppState.getInstance(this).getModel();
+        mOnboardingPrefs = new OnboardingPrefs<>(this, Utilities.getPrefs(this));
+        mSecondaryDisplayPredictions = SecondaryDisplayPredictions.newInstance(this);
         if (getWindow().getDecorView().isAttachedToWindow()) {
             initUi();
         }
@@ -204,6 +213,7 @@
             mAppDrawerShown = true;
             mAppsView.setVisibility(View.VISIBLE);
             mAppsButton.setVisibility(View.INVISIBLE);
+            mSecondaryDisplayPredictions.updateAppDivider();
         } else {
             mAppDrawerShown = false;
             animator.addListener(new AnimatorListenerAdapter() {
@@ -219,6 +229,26 @@
     }
 
     @Override
+    public OnboardingPrefs<?> getOnboardingPrefs() {
+        return mOnboardingPrefs;
+    }
+
+    @Override
+    public void startBinding() {
+        mBindingItems = true;
+    }
+
+    @Override
+    public boolean isBindingItems() {
+        return mBindingItems;
+    }
+
+    @Override
+    public void finishBindingItems(IntSet pagesBoundFirst) {
+        mBindingItems = false;
+    }
+
+    @Override
     public void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap) {
         mPopupDataProvider.setDeepShortcutMap(deepShortcutMap);
     }
@@ -230,6 +260,13 @@
     }
 
     @Override
+    public void bindExtraContainerItems(BgDataModel.FixedContainerItems item) {
+        if (item.containerId == LauncherSettings.Favorites.CONTAINER_PREDICTION) {
+            mSecondaryDisplayPredictions.setPredictedApps(item);
+        }
+    }
+
+    @Override
     public StringCache getStringCache() {
         return mStringCache;
     }
@@ -259,7 +296,7 @@
             Intent intent;
             if (item instanceof ItemInfoWithIcon
                     && (((ItemInfoWithIcon) item).runtimeStatusFlags
-                        & ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE) != 0) {
+                    & ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE) != 0) {
                 ItemInfoWithIcon appInfo = (ItemInfoWithIcon) item;
                 intent = appInfo.getMarketIntent(this);
             } else {
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictions.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictions.java
new file mode 100644
index 0000000..a58916a
--- /dev/null
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictions.java
@@ -0,0 +1,48 @@
+/*
+ * 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.secondarydisplay;
+
+import android.content.Context;
+
+import com.android.launcher3.R;
+import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.util.ResourceBasedOverride;
+
+/**
+ * Exposes Quickstep app prediction row APIs to {@link SecondaryDisplayLauncher}.
+ */
+public class SecondaryDisplayPredictions implements ResourceBasedOverride {
+    /**
+     * Creates a {@link SecondaryDisplayPredictions} instance.
+     */
+    static SecondaryDisplayPredictions newInstance(Context context) {
+        return Overrides.getObject(
+                SecondaryDisplayPredictions.class, context,
+                R.string.secondary_display_predictions_class);
+    }
+
+    /**
+     * Setup/update app divider separating app predictions from All Apps.
+     */
+    void updateAppDivider() {
+    }
+
+    /**
+     * Set predicted apps in top of app drawer.
+     */
+    public void setPredictedApps(BgDataModel.FixedContainerItems item) {
+    }
+}
diff --git a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
index b06b8a1..6057586 100644
--- a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
+++ b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
@@ -63,6 +63,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.config.FlagTogglerPrefUi;
+import com.android.launcher3.secondarydisplay.SecondaryDisplayLauncher;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.util.OnboardingPrefs;
 
@@ -369,6 +370,16 @@
             return true;
         });
         sandboxCategory.addPreference(launchSandboxModeTutorialPreference);
+
+        Preference launchSecondaryDisplayPreference = new Preference(context);
+        launchSecondaryDisplayPreference.setKey("launchSecondaryDisplay");
+        launchSecondaryDisplayPreference.setTitle("Launch Secondary Display");
+        launchSecondaryDisplayPreference.setSummary("Launch secondary display activity");
+        launchSecondaryDisplayPreference.setOnPreferenceClickListener(preference -> {
+            startActivity(new Intent(context, SecondaryDisplayLauncher.class));
+            return true;
+        });
+        sandboxCategory.addPreference(launchSecondaryDisplayPreference);
     }
 
     private void addOnboardingPrefsCatergory() {
diff --git a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
index c166bfc..0306730 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
@@ -16,7 +16,6 @@
 
 package com.android.launcher3.shortcuts;
 
-import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -24,7 +23,6 @@
 import android.view.View;
 
 import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.graphics.DragPreviewProvider;
 import com.android.launcher3.icons.BitmapRenderer;
 import com.android.launcher3.icons.FastBitmapDrawable;
@@ -44,33 +42,13 @@
 
     @Override
     public Drawable createDrawable() {
-        if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
-            int size = ActivityContext.lookupContext(mView.getContext())
-                    .getDeviceProfile().iconSizePx;
-            return new FastBitmapDrawable(
-                    BitmapRenderer.createHardwareBitmap(
-                            size + blurSizeOutline,
-                            size + blurSizeOutline,
-                            (c) -> drawDragViewOnBackground(c, size)));
-        } else {
-            return new FastBitmapDrawable(createDragBitmapLegacy());
-        }
-    }
-
-    private Bitmap createDragBitmapLegacy() {
-        Drawable d = mView.getBackground();
-        Rect bounds = getDrawableBounds(d);
-        int size = ActivityContext.lookupContext(mView.getContext()).getDeviceProfile().iconSizePx;
-        final Bitmap b = Bitmap.createBitmap(
-                size + blurSizeOutline,
-                size + blurSizeOutline,
-                Bitmap.Config.ARGB_8888);
-        Canvas canvas = new Canvas(b);
-        canvas.translate(blurSizeOutline / 2, blurSizeOutline / 2);
-        canvas.scale(((float) size) / bounds.width(), ((float) size) / bounds.height(), 0, 0);
-        canvas.translate(bounds.left, bounds.top);
-        d.draw(canvas);
-        return b;
+        int size = ActivityContext.lookupContext(mView.getContext())
+                .getDeviceProfile().iconSizePx;
+        return new FastBitmapDrawable(
+                BitmapRenderer.createHardwareBitmap(
+                        size + blurSizeOutline,
+                        size + blurSizeOutline,
+                        (c) -> drawDragViewOnBackground(c, size)));
     }
 
     private void drawDragViewOnBackground(Canvas canvas, float size) {
diff --git a/src/com/android/launcher3/shortcuts/ShortcutRequest.java b/src/com/android/launcher3/shortcuts/ShortcutRequest.java
index 5291ce4..07d3292 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutRequest.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutRequest.java
@@ -16,7 +16,7 @@
 
 package com.android.launcher3.shortcuts;
 
-import static com.android.launcher3.model.WidgetsModel.GO_DISABLE_WIDGETS;
+import static com.android.launcher3.model.WidgetsModel.GO_DISABLE_SHORTCUTS;
 
 import android.content.ComponentName;
 import android.content.Context;
@@ -46,7 +46,7 @@
             | ShortcutQuery.FLAG_MATCH_MANIFEST;
     public static final int PINNED = ShortcutQuery.FLAG_MATCH_PINNED;
 
-    private final ShortcutQuery mQuery = GO_DISABLE_WIDGETS ? null : new ShortcutQuery();
+    private final ShortcutQuery mQuery = GO_DISABLE_SHORTCUTS ? null : new ShortcutQuery();
 
     private final Context mContext;
     private final UserHandle mUserHandle;
@@ -73,7 +73,7 @@
      * @return A list of ShortcutInfo's associated with the given package.
      */
     public ShortcutRequest forPackage(String packageName, @Nullable List<String> shortcutIds) {
-        if (!GO_DISABLE_WIDGETS && packageName != null) {
+        if (!GO_DISABLE_SHORTCUTS && packageName != null) {
             mQuery.setPackage(packageName);
             mQuery.setShortcutIds(shortcutIds);
         }
@@ -81,7 +81,7 @@
     }
 
     public ShortcutRequest withContainer(@Nullable ComponentName activity) {
-        if (!GO_DISABLE_WIDGETS) {
+        if (!GO_DISABLE_SHORTCUTS) {
             if (activity == null) {
                 mFailed = true;
             } else {
@@ -92,7 +92,7 @@
     }
 
     public QueryResult query(int flags) {
-        if (GO_DISABLE_WIDGETS || mFailed) {
+        if (GO_DISABLE_SHORTCUTS || mFailed) {
             return QueryResult.DEFAULT;
         }
         mQuery.setQueryFlags(flags);
@@ -108,7 +108,7 @@
 
     public static class QueryResult extends ArrayList<ShortcutInfo> {
 
-        static final QueryResult DEFAULT = new QueryResult(GO_DISABLE_WIDGETS);
+        static final QueryResult DEFAULT = new QueryResult(GO_DISABLE_SHORTCUTS);
 
         private final boolean mWasSuccess;
 
diff --git a/src/com/android/launcher3/statemanager/BaseState.java b/src/com/android/launcher3/statemanager/BaseState.java
index f9a36ad..32378b8 100644
--- a/src/com/android/launcher3/statemanager/BaseState.java
+++ b/src/com/android/launcher3/statemanager/BaseState.java
@@ -63,4 +63,11 @@
     default boolean displayOverviewTasksAsGrid(DeviceProfile deviceProfile) {
         return false;
     }
+
+    /**
+     * For this state, whether tasks should show the thumbnail splash.
+     */
+    default boolean showTaskThumbnailSplash() {
+        return false;
+    }
 }
diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java
index 2aa9dde..86277a7 100644
--- a/src/com/android/launcher3/statemanager/StateManager.java
+++ b/src/com/android/launcher3/statemanager/StateManager.java
@@ -342,6 +342,7 @@
             public void onAnimationSuccess(Animator animator) {
                 onStateTransitionEnd(state);
             }
+
         };
     }
 
diff --git a/src/com/android/launcher3/statemanager/StatefulActivity.java b/src/com/android/launcher3/statemanager/StatefulActivity.java
index c554d06..2a890c3 100644
--- a/src/com/android/launcher3/statemanager/StatefulActivity.java
+++ b/src/com/android/launcher3/statemanager/StatefulActivity.java
@@ -15,19 +15,27 @@
  */
 package com.android.launcher3.statemanager;
 
+import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
+import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
+
+import static com.android.launcher3.LauncherState.FLAG_CLOSE_POPUPS;
 import static com.android.launcher3.LauncherState.FLAG_NON_INTERACTIVE;
 
+import android.content.res.Configuration;
+import android.os.Bundle;
 import android.os.Handler;
 import android.view.LayoutInflater;
 import android.view.View;
 
 import androidx.annotation.CallSuper;
 
+import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.LauncherRootView;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
 import com.android.launcher3.statemanager.StateManager.StateHandler;
+import com.android.launcher3.util.window.WindowManagerProxy;
 import com.android.launcher3.views.BaseDragLayer;
 
 import java.util.List;
@@ -45,6 +53,17 @@
 
     private LauncherRootView mRootView;
 
+    protected Configuration mOldConfig;
+    private int mOldRotation;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mOldConfig = new Configuration(getResources().getConfiguration());
+        mOldRotation = WindowManagerProxy.INSTANCE.get(this).getRotation(this);
+    }
+
     /**
      * Create handlers to control the property changes for this activity
      */
@@ -87,6 +106,10 @@
         if (mDeferredResumePending) {
             handleDeferredResume();
         }
+
+        if (state.hasFlag(FLAG_CLOSE_POPUPS)) {
+            AbstractFloatingView.closeAllOpenViews(this, !state.hasFlag(FLAG_NON_INTERACTIVE));
+        }
     }
 
     /**
@@ -180,4 +203,32 @@
     public void runOnBindToTouchInteractionService(Runnable r) {
         r.run();
     }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        handleConfigurationChanged(newConfig);
+        super.onConfigurationChanged(newConfig);
+    }
+
+    /**
+     * Handles configuration change when system calls {@link #onConfigurationChanged}, or on other
+     * situations that configuration might change.
+     */
+    public void handleConfigurationChanged(Configuration newConfig) {
+        int diff = newConfig.diff(mOldConfig);
+        int rotation = WindowManagerProxy.INSTANCE.get(this).getRotation(this);
+        if ((diff & (CONFIG_ORIENTATION | CONFIG_SCREEN_SIZE)) != 0
+                || rotation != mOldRotation) {
+            onHandleConfigurationChanged();
+        }
+
+        mOldConfig.setTo(newConfig);
+        mOldRotation = rotation;
+    }
+
+    /**
+     * Logic for when device configuration changes (rotation, screen size change, multi-window,
+     * etc.)
+     */
+    protected abstract void onHandleConfigurationChanged();
 }
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index 38b62d4..b94ea07 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -21,23 +21,29 @@
 import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE;
 
 import static com.android.launcher3.Utilities.dpiFromPx;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.window.WindowManagerProxy.MIN_TABLET_WIDTH;
 
+import android.app.Activity;
+import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.os.Handler;
+import android.os.Message;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
 
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.util.UiThreadHelper;
+import com.android.launcher3.util.DisplayController;
 
 /**
  * Utility class to manage launcher rotation
  */
 public class RotationHelper implements OnSharedPreferenceChangeListener,
-        DeviceProfile.OnDeviceProfileChangeListener {
-
-    private static final String TAG = "RotationHelper";
+        DisplayController.DisplayInfoChangeListener {
 
     public static final String ALLOW_ROTATION_PREFERENCE_KEY = "pref_allowRotation";
 
@@ -56,8 +62,10 @@
     public static final int REQUEST_ROTATE = 1;
     public static final int REQUEST_LOCK = 2;
 
+    @Nullable
     private BaseActivity mActivity;
     private SharedPreferences mSharedPrefs = null;
+    private final Handler mRequestOrientationHandler;
 
     private boolean mIgnoreAutoRotateSettings;
     private boolean mForceAllowRotationForTesting;
@@ -87,6 +95,8 @@
 
     public RotationHelper(BaseActivity activity) {
         mActivity = activity;
+        mRequestOrientationHandler =
+                new Handler(UI_HELPER_EXECUTOR.getLooper(), this::setOrientationAsync);
     }
 
     private void setIgnoreAutoRotateSettings(boolean ignoreAutoRotateSettings) {
@@ -119,8 +129,8 @@
     }
 
     @Override
-    public void onDeviceProfileChanged(DeviceProfile dp) {
-        boolean ignoreAutoRotateSettings = dp.isTablet;
+    public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) {
+        boolean ignoreAutoRotateSettings = info.isTablet(info.realBounds);
         if (mIgnoreAutoRotateSettings != ignoreAutoRotateSettings) {
             setIgnoreAutoRotateSettings(ignoreAutoRotateSettings);
             notifyChange();
@@ -157,8 +167,10 @@
     public void initialize() {
         if (!mInitialized) {
             mInitialized = true;
-            setIgnoreAutoRotateSettings(mActivity.getDeviceProfile().isTablet);
-            mActivity.addOnDeviceProfileChangeListener(this);
+            DisplayController displayController = DisplayController.INSTANCE.get(mActivity);
+            DisplayController.Info info = displayController.getInfo();
+            setIgnoreAutoRotateSettings(info.isTablet(info.realBounds));
+            displayController.addChangeListener(this);
             notifyChange();
         }
     }
@@ -166,7 +178,7 @@
     public void destroy() {
         if (!mDestroyed) {
             mDestroyed = true;
-            mActivity.removeOnDeviceProfileChangeListener(this);
+            DisplayController.INSTANCE.get(mActivity).removeChangeListener(this);
             mActivity = null;
             if (mSharedPrefs != null) {
                 mSharedPrefs.unregisterOnSharedPreferenceChangeListener(this);
@@ -198,10 +210,19 @@
         }
         if (activityFlags != mLastActivityFlags) {
             mLastActivityFlags = activityFlags;
-            UiThreadHelper.setOrientationAsync(mActivity, activityFlags);
+            mRequestOrientationHandler.sendEmptyMessage(activityFlags);
         }
     }
 
+    @WorkerThread
+    private boolean setOrientationAsync(Message msg) {
+        Activity activity = mActivity;
+        if (activity != null) {
+            activity.setRequestedOrientation(msg.what);
+        }
+        return true;
+    }
+
     /**
      * @return how many factors {@param newRotation} is rotated 90 degrees clockwise.
      * E.g. 1->Rotated by 90 degrees clockwise, 2->Rotated 180 clockwise...
diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java
index a205ab5..3286afb 100644
--- a/src/com/android/launcher3/states/SpringLoadedState.java
+++ b/src/com/android/launcher3/states/SpringLoadedState.java
@@ -31,8 +31,7 @@
 
     private static final int STATE_FLAGS = FLAG_MULTI_PAGE
             | FLAG_WORKSPACE_INACCESSIBLE | FLAG_DISABLE_RESTORE
-            | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED | FLAG_WORKSPACE_HAS_BACKGROUNDS
-            | FLAG_HIDE_BACK_BUTTON;
+            | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED | FLAG_WORKSPACE_HAS_BACKGROUNDS;
 
     public SpringLoadedState(int id) {
         super(id, LAUNCHER_STATE_HOME, STATE_FLAGS);
@@ -52,13 +51,13 @@
         }
 
         float shrunkTop = grid.getCellLayoutSpringLoadShrunkTop();
-        float scale = grid.getWorkspaceSpringLoadScale();
+        float scale = grid.getWorkspaceSpringLoadScale(launcher);
 
         float halfHeight = ws.getHeight() / 2;
         float myCenter = ws.getTop() + halfHeight;
         float cellTopFromCenter = halfHeight - ws.getChildAt(0).getTop();
         float actualCellTop = myCenter - cellTopFromCenter * scale;
-        return new ScaleAndTranslation(scale, 0, (shrunkTop - actualCellTop) / scale);
+        return new ScaleAndTranslation(scale, 0, shrunkTop - actualCellTop);
     }
 
     @Override
diff --git a/src/com/android/launcher3/states/StateAnimationConfig.java b/src/com/android/launcher3/states/StateAnimationConfig.java
index f99519d..54735f0 100644
--- a/src/com/android/launcher3/states/StateAnimationConfig.java
+++ b/src/com/android/launcher3/states/StateAnimationConfig.java
@@ -64,6 +64,8 @@
             ANIM_DEPTH,
             ANIM_OVERVIEW_ACTIONS_FADE,
             ANIM_WORKSPACE_PAGE_TRANSLATE_X,
+            ANIM_OVERVIEW_SPLIT_SELECT_FLOATING_TASK_TRANSLATE_OFFSCREEN,
+            ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface AnimType {}
@@ -84,8 +86,10 @@
     public static final int ANIM_DEPTH = 13;
     public static final int ANIM_OVERVIEW_ACTIONS_FADE = 14;
     public static final int ANIM_WORKSPACE_PAGE_TRANSLATE_X = 15;
+    public static final int ANIM_OVERVIEW_SPLIT_SELECT_FLOATING_TASK_TRANSLATE_OFFSCREEN = 17;
+    public static final int ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE = 18;
 
-    private static final int ANIM_TYPES_COUNT = 17;
+    private static final int ANIM_TYPES_COUNT = 19;
 
     protected final Interpolator[] mInterpolators = new Interpolator[ANIM_TYPES_COUNT];
 
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index 242d2d4..269baf0 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;
@@ -40,6 +41,9 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.testing.shared.HotseatCellCenterRequest;
+import com.android.launcher3.testing.shared.TestProtocol;
+import com.android.launcher3.testing.shared.WorkspaceCellCenterRequest;
 import com.android.launcher3.util.ResourceBasedOverride;
 import com.android.launcher3.widget.picker.WidgetsFullSheet;
 
@@ -108,12 +112,12 @@
 
             case TestProtocol.REQUEST_APPS_LIST_SCROLL_Y: {
                 return getLauncherUIProperty(Bundle::putInt,
-                        l -> l.getAppsView().getActiveRecyclerView().getCurrentScrollY());
+                        l -> l.getAppsView().getActiveRecyclerView().computeVerticalScrollOffset());
             }
 
             case TestProtocol.REQUEST_WIDGETS_SCROLL_Y: {
                 return getLauncherUIProperty(Bundle::putInt,
-                        l -> WidgetsFullSheet.getWidgetsView(l).getCurrentScrollY());
+                        l -> WidgetsFullSheet.getWidgetsView(l).computeVerticalScrollOffset());
             }
 
             case TestProtocol.REQUEST_TARGET_INSETS: {
@@ -153,10 +157,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 +185,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 +197,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.top + (cellRect.bottom - cellRect.top) / 3);
+                });
+            }
 
             case TestProtocol.REQUEST_HAS_TIS: {
                 response.putBoolean(
diff --git a/src/com/android/launcher3/testing/TestInformationProvider.java b/src/com/android/launcher3/testing/TestInformationProvider.java
index bcc7c2d..5444d92 100644
--- a/src/com/android/launcher3/testing/TestInformationProvider.java
+++ b/src/com/android/launcher3/testing/TestInformationProvider.java
@@ -21,10 +21,14 @@
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
+import android.util.Log;
 
 import com.android.launcher3.Utilities;
 
 public class TestInformationProvider extends ContentProvider {
+
+    private static final String TAG = "TestInformationProvider";
+
     @Override
     public boolean onCreate() {
         return true;
@@ -60,7 +64,13 @@
         if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
             TestInformationHandler handler = TestInformationHandler.newInstance(getContext());
             handler.init(getContext());
-            return handler.call(method, arg, extras);
+
+            Bundle response =  handler.call(method, arg, extras);
+            if (response == null) {
+                Log.e(TAG, "Couldn't handle method: " + method + "; current handler="
+                        + handler.getClass().getSimpleName());
+            }
+            return response;
         }
         return null;
     }
diff --git a/src/com/android/launcher3/testing/TestLogging.java b/src/com/android/launcher3/testing/TestLogging.java
index 103b565..c151606 100644
--- a/src/com/android/launcher3/testing/TestLogging.java
+++ b/src/com/android/launcher3/testing/TestLogging.java
@@ -22,6 +22,7 @@
 import android.view.MotionEvent;
 
 import com.android.launcher3.Utilities;
+import com.android.launcher3.testing.shared.TestProtocol;
 
 import java.util.function.BiConsumer;
 
diff --git a/src/com/android/launcher3/testing/shared/HotseatCellCenterRequest.java b/src/com/android/launcher3/testing/shared/HotseatCellCenterRequest.java
new file mode 100644
index 0000000..7eb035a
--- /dev/null
+++ b/src/com/android/launcher3/testing/shared/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.shared;
+
+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/ResourceUtils.java b/src/com/android/launcher3/testing/shared/ResourceUtils.java
similarity index 69%
rename from src/com/android/launcher3/ResourceUtils.java
rename to src/com/android/launcher3/testing/shared/ResourceUtils.java
index f709aca..d0ae258 100644
--- a/src/com/android/launcher3/ResourceUtils.java
+++ b/src/com/android/launcher3/testing/shared/ResourceUtils.java
@@ -14,13 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.launcher3;
+package com.android.launcher3.testing.shared;
 
 import android.content.res.Resources;
 import android.util.DisplayMetrics;
 import android.util.TypedValue;
 
 public class ResourceUtils {
+    private static final float EPSILON = 0.0001f;
     public static final int DEFAULT_NAVBAR_VALUE = 48;
     public static final int INVALID_RESOURCE_HANDLE = -1;
     public static final String NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE = "navigation_bar_width";
@@ -35,6 +36,8 @@
     public static final String STATUS_BAR_HEIGHT_LANDSCAPE = "status_bar_height_landscape";
     public static final String STATUS_BAR_HEIGHT_PORTRAIT = "status_bar_height_portrait";
 
+    public static final String NAV_BAR_INTERACTION_MODE_RES_NAME = "config_navBarInteractionMode";
+
     public static int getNavbarSize(String resName, Resources res) {
         return getDimenByName(resName, res, DEFAULT_NAVBAR_VALUE);
     }
@@ -71,7 +74,25 @@
     }
 
     public static int pxFromDp(float size, DisplayMetrics metrics, float scale) {
-        return size < 0 ? INVALID_RESOURCE_HANDLE : Math.round(scale
-                * TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, size, metrics));
+        float value = scale * TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, size, metrics);
+        return size < 0 ? INVALID_RESOURCE_HANDLE : roundPxValueFromFloat(value);
+    }
+
+    /**
+     * Rounds a pixel value, taking into account floating point errors.
+     *
+     * <p>If a dp (or sp) value typically returns a half pixel, such as 20dp at a 2.625 density
+     * returning 52.5px, there is a small chance that due to floating-point errors, the value will
+     * be stored as 52.499999. As we round to the nearest pixel, this could cause a 1px difference
+     * in final values, which we correct for in this method.
+     */
+    public static int roundPxValueFromFloat(float value) {
+        float fraction = (float) (value - Math.floor(value));
+        if (Math.abs(0.5f - fraction) < EPSILON) {
+            // Note: we add for negative values as well, as Math.round brings -.5 to the next
+            // "highest" value, e.g. Math.round(-2.5) == -2 [i.e. (int)Math.floor(a + 0.5d)]
+            value += EPSILON;
+        }
+        return Math.round(value);
     }
 }
diff --git a/src/com/android/launcher3/testing/TestInformationRequest.java b/src/com/android/launcher3/testing/shared/TestInformationRequest.java
similarity index 94%
rename from src/com/android/launcher3/testing/TestInformationRequest.java
rename to src/com/android/launcher3/testing/shared/TestInformationRequest.java
index 272ae56..38282032 100644
--- a/src/com/android/launcher3/testing/TestInformationRequest.java
+++ b/src/com/android/launcher3/testing/shared/TestInformationRequest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.launcher3.testing;
+package com.android.launcher3.testing.shared;
 
 import android.os.Parcelable;
 
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/shared/TestProtocol.java
similarity index 91%
rename from src/com/android/launcher3/testing/TestProtocol.java
rename to src/com/android/launcher3/testing/shared/TestProtocol.java
index 3a030a8..5116b01 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/shared/TestProtocol.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.launcher3.testing;
+package com.android.launcher3.testing.shared;
 
 /**
  * Protocol for custom accessibility events for communication with UI Automation tests.
@@ -86,6 +86,7 @@
     public static final String REQUEST_DISABLE_MANUAL_TASKBAR_STASHING = "disable-taskbar-stashing";
     public static final String REQUEST_UNSTASH_TASKBAR_IF_STASHED = "unstash-taskbar-if-stashed";
     public static final String REQUEST_STASHED_TASKBAR_HEIGHT = "stashed-taskbar-height";
+    public static final String REQUEST_RECREATE_TASKBAR = "recreate-taskbar";
     public static final String REQUEST_APP_LIST_FREEZE_FLAGS = "app-list-freeze-flags";
     public static final String REQUEST_APPS_LIST_SCROLL_Y = "apps-list-scroll-y";
     public static final String REQUEST_WIDGETS_SCROLL_Y = "widgets-scroll-y";
@@ -115,6 +116,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 +125,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";
@@ -137,6 +137,11 @@
     public static final String NO_DROP_TARGET = "b/195031154";
     public static final String NULL_INT_SET = "b/200572078";
     public static final String MISSING_PROMISE_ICON = "b/202985412";
-    public static final String BAD_STATE = "b/223498680";
     public static final String TASKBAR_IN_APP_STATE = "b/227657604";
+    public static final String INCORRECT_INFO_UPDATED = "b/239465630";
+
+    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/testing/WorkspaceCellCenterRequest.java b/src/com/android/launcher3/testing/shared/WorkspaceCellCenterRequest.java
similarity index 98%
rename from src/com/android/launcher3/testing/WorkspaceCellCenterRequest.java
rename to src/com/android/launcher3/testing/shared/WorkspaceCellCenterRequest.java
index 71ab09f..80dbef8 100644
--- a/src/com/android/launcher3/testing/WorkspaceCellCenterRequest.java
+++ b/src/com/android/launcher3/testing/shared/WorkspaceCellCenterRequest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.launcher3.testing;
+package com.android.launcher3.testing.shared;
 
 import android.os.Parcel;
 import android.os.Parcelable;
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 09b8228..8f7a4ec 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -32,6 +32,7 @@
 
 import android.animation.Animator.AnimatorListener;
 import android.animation.ValueAnimator;
+import android.util.Log;
 import android.view.MotionEvent;
 
 import com.android.launcher3.Launcher;
@@ -211,6 +212,10 @@
                     mFlingBlockCheck.blockFling();
                 }
             }
+            if (mFromState == LauncherState.ALL_APPS) {
+                mAllAppsOvershootStarted = true;
+                mLauncher.getAppsView().onPull(-progress , -progress);
+            }
         } else if (progress >= 1) {
             if (reinitCurrentAnimation(true, isDragTowardPositive)) {
                 mDisplacementShift = displacement;
diff --git a/src/com/android/launcher3/touch/AllAppsSwipeController.java b/src/com/android/launcher3/touch/AllAppsSwipeController.java
index db43baa..5279dec 100644
--- a/src/com/android/launcher3/touch/AllAppsSwipeController.java
+++ b/src/com/android/launcher3/touch/AllAppsSwipeController.java
@@ -18,6 +18,7 @@
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.anim.Interpolators.DECELERATED_EASE;
+import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
 import static com.android.launcher3.anim.Interpolators.EMPHASIZED_ACCELERATE;
 import static com.android.launcher3.anim.Interpolators.EMPHASIZED_DECELERATE;
 import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
@@ -47,48 +48,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 +182,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 +192,79 @@
     }
 
     /**
-     * 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);
+            }
+            config.setInterpolator(ANIM_WORKSPACE_SCALE, EMPHASIZED);
+            config.setInterpolator(ANIM_DEPTH, EMPHASIZED);
+        } 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);
+            }
+            config.setInterpolator(ANIM_WORKSPACE_SCALE, EMPHASIZED);
+            config.setInterpolator(ANIM_DEPTH, EMPHASIZED);
         } 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/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index e95a787..b4be061 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -61,7 +61,7 @@
 import com.android.launcher3.pm.InstallSessionHelper;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.testing.TestLogging;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.views.FloatingIconView;
@@ -248,13 +248,19 @@
             final Launcher launcher = Launcher.getLauncher(context);
             if (shortcut.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
                     && shortcut.isDisabledVersionLower()) {
+                final Intent marketIntent = shortcut.getMarketIntent(context);
+                // No market intent means no target package for the shortcut, which should be an
+                // issue. Falling back to showing toast messages.
+                if (marketIntent == null) {
+                    return false;
+                }
 
                 new AlertDialog.Builder(context)
                         .setTitle(R.string.dialog_update_title)
                         .setMessage(R.string.dialog_update_message)
                         .setPositiveButton(R.string.dialog_update, (d, i) -> {
                             // Direct the user to the play store to update the app
-                            context.startActivity(shortcut.getMarketIntent(context));
+                            context.startActivity(marketIntent);
                         })
                         .setNeutralButton(R.string.dialog_remove, (d, i) -> {
                             // Remove the icon if launcher is successfully initialized
diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java
index 6bae745..1421ece 100644
--- a/src/com/android/launcher3/touch/ItemLongClickListener.java
+++ b/src/com/android/launcher3/touch/ItemLongClickListener.java
@@ -27,7 +27,6 @@
 import android.view.View.OnLongClickListener;
 
 import com.android.launcher3.CellLayout;
-import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.DropTarget;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.dragndrop.DragController;
@@ -36,8 +35,9 @@
 import com.android.launcher3.logging.StatsLogManager.StatsLogger;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.testing.TestLogging;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.views.BubbleTextHolder;
+import com.android.launcher3.widget.LauncherAppWidgetHostView;
 
 /**
  * Class to handle long-clicks on workspace items and start drag as a result.
@@ -51,7 +51,11 @@
             ItemLongClickListener::onAllAppsItemLongClick;
 
     private static boolean onWorkspaceItemLongClick(View v) {
-        TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onWorkspaceItemLongClick");
+        if (v instanceof LauncherAppWidgetHostView) {
+            TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "Widgets.onLongClick");
+        } else {
+            TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onWorkspaceItemLongClick");
+        }
         Launcher launcher = Launcher.getLauncher(v.getContext());
         if (!canStartDrag(launcher)) return false;
         if (!launcher.isInState(NORMAL) && !launcher.isInState(OVERVIEW)) return false;
@@ -113,10 +117,7 @@
             }
         });
 
-        DeviceProfile grid = launcher.getDeviceProfile();
-        DragOptions options = new DragOptions();
-        options.intrinsicIconScaleFactor = (float) grid.allAppsIconSizePx / grid.iconSizePx;
-        launcher.getWorkspace().beginDragShared(v, launcher.getAppsView(), options);
+        launcher.getWorkspace().beginDragShared(v, launcher.getAppsView(), new DragOptions());
         return false;
     }
 
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index b477905..9afca4f 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;
@@ -375,6 +376,19 @@
         return isRtl ? 1 : -1;
     }
 
+    @Override
+    public void setSecondaryTaskMenuPosition(SplitBounds splitBounds, View taskView,
+            DeviceProfile deviceProfile, View primarySnaphotView, View taskMenuView) {
+        float topLeftTaskPlusDividerPercent = splitBounds.appsStackedVertically
+                ? (splitBounds.topTaskPercent + splitBounds.dividerHeightPercent)
+                : (splitBounds.leftTaskPercent + splitBounds.dividerWidthPercent);
+        FrameLayout.LayoutParams snapshotParams =
+                (FrameLayout.LayoutParams) primarySnaphotView.getLayoutParams();
+        float additionalOffset = (taskView.getHeight() - snapshotParams.topMargin)
+                * topLeftTaskPlusDividerPercent;
+        taskMenuView.setY(taskMenuView.getY() + additionalOffset);
+    }
+
     /* -------------------- */
 
     @Override
@@ -424,7 +438,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 +450,28 @@
     }
 
     @Override
+    public void setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight,
+            int splitInstructionsWidth, int threeButtonNavShift) {
+        out.setPivotX(0);
+        out.setPivotY(splitInstructionsHeight);
+        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(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 +483,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,13 +500,13 @@
 
     @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;
         int dividerBar = splitBoundsConfig.appsStackedVertically
-                ? splitBoundsConfig.visualDividerBounds.height()
-                : splitBoundsConfig.visualDividerBounds.width();
+                ? (int) (splitBoundsConfig.dividerHeightPercent * parentHeight)
+                : (int) (splitBoundsConfig.dividerWidthPercent * parentWidth);
         int primarySnapshotHeight;
         int primarySnapshotWidth;
         int secondarySnapshotHeight;
@@ -500,13 +536,14 @@
         iconParams.rightMargin = -taskIconHeight - taskIconMargin / 2;
         iconParams.leftMargin = 0;
         iconParams.topMargin = thumbnailTopMargin / 2;
+        iconParams.bottomMargin = 0;
     }
 
     @Override
     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 +552,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;
@@ -556,4 +594,22 @@
             FloatProperty secondary, DeviceProfile deviceProfile) {
         return new Pair<>(primary, secondary);
     }
+
+    @Override
+    public float getFloatingTaskOffscreenTranslationTarget(View floatingTask, RectF onScreenRect,
+            @StagePosition int stagePosition, DeviceProfile dp) {
+        float currentTranslationY = floatingTask.getTranslationY();
+        return currentTranslationY - onScreenRect.height();
+    }
+
+    @Override
+    public void setFloatingTaskPrimaryTranslation(View floatingTask, float translation,
+            DeviceProfile dp) {
+        floatingTask.setTranslationY(translation);
+    }
+
+    @Override
+    public Float getFloatingTaskPrimaryTranslation(View floatingTask, DeviceProfile dp) {
+        return floatingTask.getTranslationY();
+    }
 }
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index ca46aa8..cbcb700 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -34,9 +34,9 @@
 
 import com.android.launcher3.DeviceProfile;
 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 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.
@@ -221,6 +231,14 @@
     int getTaskDragDisplacementFactor(boolean isRtl);
 
     /**
+     * Calls the corresponding {@link View#setX(float)} or {@link View#setY(float)}
+     * on {@param taskMenuView} by taking the space needed by {@param primarySnapshotView} into
+     * account.
+     * This is expected to only be called for secondary (bottom/right) tasks.
+     */
+    void setSecondaryTaskMenuPosition(SplitBounds splitBounds, View taskView,
+            DeviceProfile deviceProfile, View primarySnaphotView, View taskMenuView);
+    /**
      * Maps the velocity from the coordinate plane of the foreground app to that
      * of Launcher's (which now will always be portrait)
      */
@@ -232,6 +250,41 @@
      */
     void fixBoundsForHomeAnimStartRect(RectF outStartRect, DeviceProfile deviceProfile);
 
+    /**
+     * Determine the target translation for animating the FloatingTaskView out. This value could
+     * either be an x-coordinate or a y-coordinate, depending on which way the FloatingTaskView was
+     * docked.
+     *
+     * @param floatingTask The FloatingTaskView.
+     * @param onScreenRect The current on-screen dimensions of the FloatingTaskView.
+     * @param stagePosition STAGE_POSITION_TOP_OR_LEFT or STAGE_POSITION_BOTTOM_OR_RIGHT.
+     * @param dp The device profile.
+     * @return A float. When an animation translates the FloatingTaskView to this position, it will
+     * appear to tuck away off the edge of the screen.
+     */
+    float getFloatingTaskOffscreenTranslationTarget(View floatingTask, RectF onScreenRect,
+            @StagePosition int stagePosition, DeviceProfile dp);
+
+    /**
+     * Sets the translation of a FloatingTaskView along its "slide-in/slide-out" axis (could be
+     * either x or y), depending on how the view is oriented.
+     *
+     * @param floatingTask The FloatingTaskView to be translated.
+     * @param translation The target translation value.
+     * @param dp The current device profile.
+     */
+    void setFloatingTaskPrimaryTranslation(View floatingTask, float translation, DeviceProfile dp);
+
+    /**
+     * Gets the translation of a FloatingTaskView along its "slide-in/slide-out" axis (could be
+     * either x or y), depending on how the view is oriented.
+     *
+     * @param floatingTask The FloatingTaskView in question.
+     * @param dp The current device profile.
+     * @return The current translation value.
+     */
+    Float getFloatingTaskPrimaryTranslation(View floatingTask, DeviceProfile dp);
+
     class ChildBounds {
 
         public final int primaryDimension;
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index 508823c..bc1b634 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.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.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.List;
@@ -313,8 +316,28 @@
     }
 
     @Override
+    public void setSecondaryTaskMenuPosition(SplitBounds splitBounds, View taskView,
+            DeviceProfile deviceProfile, View primarySnaphotView, View taskMenuView) {
+        float topLeftTaskPlusDividerPercent = splitBounds.appsStackedVertically
+                ? (splitBounds.topTaskPercent + splitBounds.dividerHeightPercent)
+                : (splitBounds.leftTaskPercent + splitBounds.dividerWidthPercent);
+        FrameLayout.LayoutParams snapshotParams =
+                (FrameLayout.LayoutParams) primarySnaphotView.getLayoutParams();
+        float additionalOffset;
+        if (deviceProfile.isLandscape) {
+            additionalOffset = (taskView.getWidth() - snapshotParams.leftMargin)
+                    * topLeftTaskPlusDividerPercent;
+            taskMenuView.setX(taskMenuView.getX() + additionalOffset);
+        } else {
+            additionalOffset = (taskView.getHeight() - snapshotParams.topMargin)
+                    * topLeftTaskPlusDividerPercent;
+            taskMenuView.setY(taskMenuView.getY() + additionalOffset);
+        }
+    }
+
+    @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 +490,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 +517,58 @@
     }
 
     @Override
+    public void setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight,
+            int splitInstructionsWidth, int threeButtonNavShift) {
+        out.setPivotX(0);
+        out.setPivotY(splitInstructionsHeight);
+        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 +600,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 +626,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;
@@ -603,15 +678,16 @@
     public void setTaskIconParams(FrameLayout.LayoutParams iconParams, int taskIconMargin,
             int taskIconHeight, int thumbnailTopMargin, boolean isRtl) {
         iconParams.gravity = TOP | CENTER_HORIZONTAL;
+        // Reset margins, since they may have been set on rotation
         iconParams.leftMargin = iconParams.rightMargin = 0;
-        iconParams.topMargin = taskIconMargin;
+        iconParams.topMargin = iconParams.bottomMargin = 0;
     }
 
     @Override
     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 =
@@ -701,4 +777,36 @@
             return new Pair<>(secondary, primary);
         }
     }
+
+    @Override
+    public float getFloatingTaskOffscreenTranslationTarget(View floatingTask, RectF onScreenRect,
+            @StagePosition int stagePosition, DeviceProfile dp) {
+        if (dp.isLandscape) {
+            float currentTranslationX = floatingTask.getTranslationX();
+            return stagePosition == STAGE_POSITION_TOP_OR_LEFT
+                    ? currentTranslationX - onScreenRect.width()
+                    : currentTranslationX + onScreenRect.width();
+        } else {
+            float currentTranslationY = floatingTask.getTranslationY();
+            return currentTranslationY - onScreenRect.height();
+        }
+    }
+
+    @Override
+    public void setFloatingTaskPrimaryTranslation(View floatingTask, float translation,
+            DeviceProfile dp) {
+        if (dp.isLandscape) {
+            floatingTask.setTranslationX(translation);
+        } else {
+            floatingTask.setTranslationY(translation);
+        }
+
+    }
+
+    @Override
+    public Float getFloatingTaskPrimaryTranslation(View floatingTask, DeviceProfile dp) {
+        return dp.isLandscape
+                ? floatingTask.getTranslationX()
+                : floatingTask.getTranslationY();
+    }
 }
diff --git a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
index 74b6a5b..55bb5e8 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,19 +187,42 @@
     }
 
     @Override
+    public void setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight,
+            int splitInstructionsWidth, int threeButtonNavShift) {
+        out.setPivotX(0);
+        out.setPivotY(splitInstructionsHeight);
+        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 - 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;
         iconParams.leftMargin = -taskIconHeight - taskIconMargin / 2;
         iconParams.rightMargin = 0;
         iconParams.topMargin = thumbnailTopMargin / 2;
+        iconParams.bottomMargin = 0;
     }
 
     @Override
     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/touch/WorkspaceTouchListener.java b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
index 17bbdf1..6cb021b 100644
--- a/src/com/android/launcher3/touch/WorkspaceTouchListener.java
+++ b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
@@ -43,7 +43,7 @@
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.testing.TestLogging;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 
 /**
  * Helper class to handle touch on empty space in workspace and show options popup on long press
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index 7c73be5..e57c88d 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -19,11 +19,7 @@
 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.Utilities.dpiFromPx;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NAVIGATION_MODE_2_BUTTON;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NAVIGATION_MODE_3_BUTTON;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NAVIGATION_MODE_GESTURE_BUTTON;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
 import static com.android.launcher3.util.window.WindowManagerProxy.MIN_TABLET_WIDTH;
@@ -41,21 +37,20 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
-import android.util.Pair;
 import android.view.Display;
 
 import androidx.annotation.AnyThread;
 import androidx.annotation.UiThread;
 
-import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
 import com.android.launcher3.util.window.CachedDisplayInfo;
 import com.android.launcher3.util.window.WindowManagerProxy;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 
@@ -66,6 +61,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);
@@ -80,7 +76,6 @@
             | CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS | CHANGE_NAVIGATION_MODE;
 
     private static final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
-    private static final String NAV_BAR_INTERACTION_MODE_RES_NAME = "config_navBarInteractionMode";
     private static final String TARGET_OVERLAY_PACKAGE = "android";
 
     private final Context mContext;
@@ -116,8 +111,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 +211,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 +234,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 +263,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;
 
@@ -292,56 +272,71 @@
         public final float fontScale;
         private final int densityDpi;
         public final NavigationMode navigationMode;
-
         private final PortraitSize mScreenSizeDp;
 
+        // WindowBounds
+        public final WindowBounds realBounds;
         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);
+                Map<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 = wmProxy.getNavigationMode(displayInfoContext);
 
             mPerDisplayBounds.putAll(perDisplayBoundsCache);
-            Pair<CachedDisplayInfo, WindowBounds[]> cachedValue = mPerDisplayBounds.get(displayId);
+            WindowBounds[] cachedValue = mPerDisplayBounds.get(normalizedDisplayInfo);
 
-            WindowBounds realBounds = wmProxy.getRealBounds(context, display, displayInfo);
+            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 +364,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)));
     }
 
     /**
@@ -403,35 +399,4 @@
         }
     }
 
-    public enum NavigationMode {
-        THREE_BUTTONS(false, 0, LAUNCHER_NAVIGATION_MODE_3_BUTTON),
-        TWO_BUTTONS(true, 1, LAUNCHER_NAVIGATION_MODE_2_BUTTON),
-        NO_BUTTON(true, 2, LAUNCHER_NAVIGATION_MODE_GESTURE_BUTTON);
-
-        public final boolean hasGestures;
-        public final int resValue;
-        public final LauncherEvent launcherEvent;
-
-        NavigationMode(boolean hasGestures, int resValue, LauncherEvent launcherEvent) {
-            this.hasGestures = hasGestures;
-            this.resValue = resValue;
-            this.launcherEvent = launcherEvent;
-        }
-    }
-
-    private static NavigationMode parseNavigationMode(Context context) {
-        int modeInt = ResourceUtils.getIntegerByName(NAV_BAR_INTERACTION_MODE_RES_NAME,
-                context.getResources(), INVALID_RESOURCE_HANDLE);
-
-        if (modeInt == INVALID_RESOURCE_HANDLE) {
-            Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
-        } else {
-            for (NavigationMode m : NavigationMode.values()) {
-                if (m.resValue == modeInt) {
-                    return m;
-                }
-            }
-        }
-        return Utilities.ATLEAST_S ? NavigationMode.NO_BUTTON : NavigationMode.THREE_BUTTONS;
-    }
 }
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/LogConfig.java b/src/com/android/launcher3/util/LogConfig.java
index 6bc26e7..ee1d1ff 100644
--- a/src/com/android/launcher3/util/LogConfig.java
+++ b/src/com/android/launcher3/util/LogConfig.java
@@ -40,4 +40,14 @@
      * When turned on, we enable suggest related logging.
      */
     public static final String SEARCH_LOGGING = "SearchLogging";
+
+    /**
+     * When turned on, we enable IME related latency related logging.
+     */
+    public static final String IME_LATENCY_LOGGING = "ImeLatencyLogging";
+
+    /**
+     * When turned on, we enable web suggest appSearch related logging.
+     */
+    public static final String WEB_APP_SEARCH_LOGGING = "WebAppSearchLogging";
 }
diff --git a/src/com/android/launcher3/util/MultiAdditivePropertyFactory.java b/src/com/android/launcher3/util/MultiPropertyFactory.java
similarity index 70%
rename from src/com/android/launcher3/util/MultiAdditivePropertyFactory.java
rename to src/com/android/launcher3/util/MultiPropertyFactory.java
index 50f7027..e7a7785 100644
--- a/src/com/android/launcher3/util/MultiAdditivePropertyFactory.java
+++ b/src/com/android/launcher3/util/MultiPropertyFactory.java
@@ -20,7 +20,6 @@
 import android.util.FloatProperty;
 import android.util.Log;
 import android.util.Property;
-import android.view.View;
 
 /**
  * Allows to combine multiple values set by several sources.
@@ -30,43 +29,55 @@
  * time.
  *
  * This class behaves similarly to [MultiValueAlpha], but is meant to be more abstract and reusable.
- * It sets the addition of all values.
+ * It aggregate all values using the provided [aggregator].
  *
  * @param <T> Type where to apply the property.
  */
-public class MultiAdditivePropertyFactory<T extends View> {
+public class MultiPropertyFactory<T> {
 
     private static final boolean DEBUG = false;
-    private static final String TAG = "MultiAdditivePropertyFactory";
+    private static final String TAG = "MultiPropertyFactory";
     private final String mName;
-    private final ArrayMap<Integer, MultiAdditiveProperty> mProperties =
-            new ArrayMap<>();
+    private final ArrayMap<Integer, MultiProperty> mProperties = new ArrayMap<>();
 
     // This is an optimization for cases when set is called repeatedly with the same setterIndex.
     private float mAggregationOfOthers = 0f;
     private Integer mLastIndexSet = -1;
-    private final Property<View, Float> mProperty;
+    private final Property<T, Float> mProperty;
+    private final FloatBiFunction mAggregator;
 
-    public MultiAdditivePropertyFactory(String name, Property<View, Float> property) {
+    /**
+     * Represents a function that accepts two float and produces a float.
+     */
+    public interface FloatBiFunction {
+        /**
+         * Applies this function to the given arguments.
+         */
+        float apply(float a, float b);
+    }
+
+    public MultiPropertyFactory(String name, Property<T, Float> property,
+            FloatBiFunction aggregator) {
         mName = name;
         mProperty = property;
+        mAggregator = aggregator;
     }
 
     /** Returns the [MultiFloatProperty] associated with [inx], creating it if not present. */
-    public MultiAdditiveProperty get(Integer index) {
+    public MultiProperty get(Integer index) {
         return mProperties.computeIfAbsent(index,
-                (k) -> new MultiAdditiveProperty(index, mName + "_" + index));
+                (k) -> new MultiProperty(index, mName + "_" + index));
     }
 
     /**
      * Each [setValue] will be aggregated with the other properties values created by the
      * corresponding factory.
      */
-    class MultiAdditiveProperty extends FloatProperty<T> {
+    class MultiProperty extends FloatProperty<T> {
         private final int mInx;
         private float mValue = 0f;
 
-        MultiAdditiveProperty(int inx, String name) {
+        MultiProperty(int inx, String name) {
             super(name);
             mInx = inx;
         }
@@ -77,12 +88,13 @@
                 mAggregationOfOthers = 0f;
                 mProperties.forEach((key, property) -> {
                     if (key != mInx) {
-                        mAggregationOfOthers += property.mValue;
+                        mAggregationOfOthers =
+                                mAggregator.apply(mAggregationOfOthers, property.mValue);
                     }
                 });
                 mLastIndexSet = mInx;
             }
-            float lastAggregatedValue = mAggregationOfOthers + newValue;
+            float lastAggregatedValue = mAggregator.apply(mAggregationOfOthers, newValue);
             mValue = newValue;
             apply(obj, lastAggregatedValue);
 
@@ -94,13 +106,13 @@
         }
 
         @Override
-        public Float get(T view) {
+        public Float get(T object) {
             // The scale of the view should match mLastAggregatedValue. Still, if it has been
             // changed without using this property, it can differ. As this get method is usually
             // used to set the starting point on an animation, this would result in some jumps
             // when the view scale is different than the last aggregated value. To stay on the
             // safe side, let's return the real view scale.
-            return mProperty.get(view);
+            return mProperty.get(object);
         }
 
         @Override
@@ -109,7 +121,7 @@
         }
     }
 
-    protected void apply(View view, float value) {
-        mProperty.set(view, value);
+    protected void apply(T object, float value) {
+        mProperty.set(object, value);
     }
 }
diff --git a/src/com/android/launcher3/util/MultiValueAlpha.java b/src/com/android/launcher3/util/MultiValueAlpha.java
index 11cd07c..4b46a0a 100644
--- a/src/com/android/launcher3/util/MultiValueAlpha.java
+++ b/src/com/android/launcher3/util/MultiValueAlpha.java
@@ -23,6 +23,7 @@
 
 import com.android.launcher3.anim.AlphaUpdateListener;
 
+import java.io.PrintWriter;
 import java.util.Arrays;
 import java.util.function.Consumer;
 
@@ -78,6 +79,29 @@
         mUpdateVisibility = updateVisibility;
     }
 
+    /**
+     * Dumps the alpha channel values to the given PrintWriter
+     *
+     * @param prefix String to be used before every line
+     * @param pw PrintWriter where the logs should be dumped
+     * @param label String used to help identify this object
+     * @param alphaIndexLabels Strings that represent each alpha channel, these should be entered
+     *                         in the order of the indexes they represent, starting from 0.
+     */
+    public void dump(String prefix, PrintWriter pw, String label, String... alphaIndexLabels) {
+        pw.println(prefix + label);
+
+        String innerPrefix = prefix + '\t';
+        for (int i = 0; i < alphaIndexLabels.length; i++) {
+            if (i >= mMyProperties.length) {
+                pw.println(innerPrefix + alphaIndexLabels[i] + " given for alpha index " + i
+                        + " however there are only " + mMyProperties.length + " alpha channels.");
+                continue;
+            }
+            pw.println(innerPrefix + alphaIndexLabels[i] + "=" + getProperty(i).getValue());
+        }
+    }
+
     public class AlphaProperty {
 
         private final int mMyMask;
diff --git a/src/com/android/launcher3/util/NavigationMode.java b/src/com/android/launcher3/util/NavigationMode.java
new file mode 100644
index 0000000..37dd41c
--- /dev/null
+++ b/src/com/android/launcher3/util/NavigationMode.java
@@ -0,0 +1,42 @@
+/*
+ * 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 static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NAVIGATION_MODE_2_BUTTON;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NAVIGATION_MODE_3_BUTTON;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NAVIGATION_MODE_GESTURE_BUTTON;
+
+import com.android.launcher3.logging.StatsLogManager;
+
+/**
+ * Navigation mode used in the device.
+ */
+public enum NavigationMode {
+    THREE_BUTTONS(false, 0, LAUNCHER_NAVIGATION_MODE_3_BUTTON),
+    TWO_BUTTONS(true, 1, LAUNCHER_NAVIGATION_MODE_2_BUTTON),
+    NO_BUTTON(true, 2, LAUNCHER_NAVIGATION_MODE_GESTURE_BUTTON);
+
+    public final boolean hasGestures;
+    public final int resValue;
+    public final StatsLogManager.LauncherEvent launcherEvent;
+
+    NavigationMode(boolean hasGestures, int resValue, StatsLogManager.LauncherEvent launcherEvent) {
+        this.hasGestures = hasGestures;
+        this.resValue = resValue;
+        this.launcherEvent = launcherEvent;
+    }
+}
diff --git a/src/com/android/launcher3/util/OnboardingPrefs.java b/src/com/android/launcher3/util/OnboardingPrefs.java
index f4cf21e..d942b7a 100644
--- a/src/com/android/launcher3/util/OnboardingPrefs.java
+++ b/src/com/android/launcher3/util/OnboardingPrefs.java
@@ -43,13 +43,14 @@
     public static final String SEARCH_ONBOARDING_COUNT = "launcher.search_onboarding_count";
     public static final String TASKBAR_EDU_SEEN = "launcher.taskbar_edu_seen";
     public static final String ALL_APPS_VISITED_COUNT = "launcher.all_apps_visited_count";
+    public static final String QSB_SEARCH_ONBOARDING_CARD_DISMISSED = "launcher.qsb_edu_dismiss";
     // When adding a new key, add it here as well, to be able to reset it from Developer Options.
     public static final Map<String, String[]> ALL_PREF_KEYS = Map.of(
             "All Apps Bounce", new String[] { HOME_BOUNCE_SEEN, HOME_BOUNCE_COUNT },
             "Hybrid Hotseat Education", new String[] { HOTSEAT_DISCOVERY_TIP_COUNT,
                     HOTSEAT_LONGPRESS_TIP_SEEN },
             "Search Education", new String[] { SEARCH_KEYBOARD_EDU_SEEN, SEARCH_SNACKBAR_COUNT,
-                    SEARCH_ONBOARDING_COUNT},
+                    SEARCH_ONBOARDING_COUNT, QSB_SEARCH_ONBOARDING_CARD_DISMISSED},
             "Taskbar Education", new String[] { TASKBAR_EDU_SEEN },
             "All Apps Visited Count", new String[] {ALL_APPS_VISITED_COUNT}
     );
@@ -61,7 +62,8 @@
             HOME_BOUNCE_SEEN,
             HOTSEAT_LONGPRESS_TIP_SEEN,
             SEARCH_KEYBOARD_EDU_SEEN,
-            TASKBAR_EDU_SEEN
+            TASKBAR_EDU_SEEN,
+            QSB_SEARCH_ONBOARDING_CARD_DISMISSED
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface EventBoolKey {}
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index f42d304..12e8b54 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -43,6 +43,9 @@
 import android.util.Pair;
 import android.widget.Toast;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.PendingAddItemInfo;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
@@ -54,6 +57,7 @@
 
 import java.net.URISyntaxException;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * Utility methods using package manager
@@ -62,22 +66,28 @@
 
     private static final String TAG = "PackageManagerHelper";
 
+    @NonNull
     private final Context mContext;
+
+    @NonNull
     private final PackageManager mPm;
+
+    @NonNull
     private final LauncherApps mLauncherApps;
 
-    public PackageManagerHelper(Context context) {
+    public PackageManagerHelper(@NonNull final Context context) {
         mContext = context;
         mPm = context.getPackageManager();
-        mLauncherApps = context.getSystemService(LauncherApps.class);
+        mLauncherApps = Objects.requireNonNull(context.getSystemService(LauncherApps.class));
     }
 
     /**
      * Returns true if the app can possibly be on the SDCard. This is just a workaround and doesn't
      * guarantee that the app is on SD card.
      */
-    public boolean isAppOnSdcard(String packageName, UserHandle user) {
-        ApplicationInfo info = getApplicationInfo(
+    public boolean isAppOnSdcard(@NonNull final String packageName,
+            @NonNull final UserHandle user) {
+        final ApplicationInfo info = getApplicationInfo(
                 packageName, user, PackageManager.MATCH_UNINSTALLED_PACKAGES);
         return info != null && (info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
     }
@@ -86,23 +96,27 @@
      * Returns whether the target app is suspended for a given user as per
      * {@link android.app.admin.DevicePolicyManager#isPackageSuspended}.
      */
-    public boolean isAppSuspended(String packageName, UserHandle user) {
-        ApplicationInfo info = getApplicationInfo(packageName, user, 0);
+    public boolean isAppSuspended(@NonNull final String packageName,
+            @NonNull final UserHandle user) {
+        final ApplicationInfo info = getApplicationInfo(packageName, user, 0);
         return info != null && isAppSuspended(info);
     }
 
     /**
      * Returns whether the target app is installed for a given user
      */
-    public boolean isAppInstalled(String packageName, UserHandle user) {
-        ApplicationInfo info = getApplicationInfo(packageName, user, 0);
+    public boolean isAppInstalled(@NonNull final String packageName,
+            @NonNull final UserHandle user) {
+        final ApplicationInfo info = getApplicationInfo(packageName, user, 0);
         return info != null;
     }
 
     /**
      * Returns the application info for the provided package or null
      */
-    public ApplicationInfo getApplicationInfo(String packageName, UserHandle user, int flags) {
+    @Nullable
+    public ApplicationInfo getApplicationInfo(@NonNull final String packageName,
+            @NonNull final UserHandle user, final int flags) {
         try {
             ApplicationInfo info = mLauncherApps.getApplicationInfo(packageName, flags, user);
             return (info.flags & ApplicationInfo.FLAG_INSTALLED) == 0 || !info.enabled
@@ -116,7 +130,8 @@
         return mPm.isSafeMode();
     }
 
-    public Intent getAppLaunchIntent(String pkg, UserHandle user) {
+    @Nullable
+    public Intent getAppLaunchIntent(@Nullable final String pkg, @NonNull final UserHandle user) {
         List<LauncherActivityInfo> activities = mLauncherApps.getActivityList(pkg, user);
         return activities.isEmpty() ? null :
                 AppInfo.makeLaunchIntent(activities.get(0));
@@ -251,7 +266,8 @@
         return packageFilter;
     }
 
-    public static boolean isSystemApp(Context context, Intent intent) {
+    public static boolean isSystemApp(@NonNull final Context context,
+            @NonNull final Intent intent) {
         PackageManager pm = context.getPackageManager();
         ComponentName cn = intent.getComponent();
         String packageName = null;
diff --git a/src/com/android/launcher3/util/PendingSplitSelectInfo.java b/src/com/android/launcher3/util/PendingSplitSelectInfo.java
new file mode 100644
index 0000000..58c3be5
--- /dev/null
+++ b/src/com/android/launcher3/util/PendingSplitSelectInfo.java
@@ -0,0 +1,52 @@
+/*
+ * 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.logging.StatsLogManager;
+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;
+    private final StatsLogManager.EventEnum mSource;
+
+    public PendingSplitSelectInfo(int stagedTaskId, int stagePosition,
+            StatsLogManager.EventEnum source) {
+        this.mStagedTaskId = stagedTaskId;
+        this.mStagePosition = stagePosition;
+        this.mSource = source;
+    }
+
+    public int getStagedTaskId() {
+        return mStagedTaskId;
+    }
+
+    public @StagePosition int getStagePosition() {
+        return mStagePosition;
+    }
+
+    public StatsLogManager.EventEnum getSource() {
+        return mSource;
+    }
+}
diff --git a/src/com/android/launcher3/util/ScrollableLayoutManager.java b/src/com/android/launcher3/util/ScrollableLayoutManager.java
new file mode 100644
index 0000000..17eaefd
--- /dev/null
+++ b/src/com/android/launcher3/util/ScrollableLayoutManager.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util;
+
+import android.content.Context;
+import android.util.SparseIntArray;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.Adapter;
+import androidx.recyclerview.widget.RecyclerView.State;
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+
+/**
+ * Extension of {@link GridLayoutManager} with support for smooth scrolling
+ */
+public class ScrollableLayoutManager extends GridLayoutManager {
+
+    // keyed on item type
+    protected final SparseIntArray mCachedSizes = new SparseIntArray();
+
+    private RecyclerView mRv;
+
+    /**
+     * Precalculated total height keyed on the item position. This is always incremental.
+     * Subclass can override {@link #incrementTotalHeight} to incorporate the layout logic.
+     * For example all-apps should have same values for items in same row,
+     *     sample values: 0, 10, 10, 10, 10, 20, 20, 20, 20
+     * whereas widgets will have strictly increasing values
+     *     sample values: 0, 10, 50, 60, 110
+     */
+
+    //
+    private int[] mTotalHeightCache = new int[1];
+    private int mLastValidHeightIndex = 0;
+
+    public ScrollableLayoutManager(Context context) {
+        super(context, 1, GridLayoutManager.VERTICAL, false);
+    }
+
+    @Override
+    public void onAttachedToWindow(RecyclerView view) {
+        super.onAttachedToWindow(view);
+        mRv = view;
+    }
+
+    @Override
+    public void layoutDecorated(@NonNull View child, int left, int top, int right, int bottom) {
+        super.layoutDecorated(child, left, top, right, bottom);
+        mCachedSizes.put(
+                mRv.getChildViewHolder(child).getItemViewType(), child.getMeasuredHeight());
+    }
+
+    @Override
+    public void layoutDecoratedWithMargins(@NonNull View child, int left, int top, int right,
+            int bottom) {
+        super.layoutDecoratedWithMargins(child, left, top, right, bottom);
+        mCachedSizes.put(
+                mRv.getChildViewHolder(child).getItemViewType(), child.getMeasuredHeight());
+    }
+
+    @Override
+    public int computeVerticalScrollExtent(State state) {
+        return mRv == null ? 0 : mRv.getHeight();
+    }
+
+    @Override
+    public int computeVerticalScrollOffset(State state) {
+        Adapter adapter = mRv == null ? null : mRv.getAdapter();
+        if (adapter == null) {
+            return 0;
+        }
+        if (adapter.getItemCount() == 0 || getChildCount() == 0) {
+            return 0;
+        }
+        View child = getChildAt(0);
+        ViewHolder holder = mRv.findContainingViewHolder(child);
+        if (holder == null) {
+            return 0;
+        }
+        int itemPosition = holder.getLayoutPosition();
+        if (itemPosition < 0) {
+            return 0;
+        }
+        return getPaddingTop() + getItemsHeight(adapter, itemPosition) - getDecoratedTop(child);
+    }
+
+    @Override
+    public int computeVerticalScrollRange(State state) {
+        Adapter adapter = mRv == null ? null : mRv.getAdapter();
+        return adapter == null ? 0 : getItemsHeight(adapter, adapter.getItemCount());
+    }
+
+    /**
+     * Returns the sum of the height, in pixels, of this list adapter's items from index
+     * 0 (inclusive) until {@code untilIndex} (exclusive). If untilIndex is same as the itemCount,
+     * it returns the full height of all the items.
+     *
+     * <p>If the untilIndex is larger than the total number of items in this adapter, returns the
+     * sum of all items' height.
+     */
+    private int getItemsHeight(Adapter adapter, int untilIndex) {
+        final int totalItems = adapter.getItemCount();
+        if (mTotalHeightCache.length < (totalItems + 1)) {
+            mTotalHeightCache = new int[totalItems + 1];
+            mLastValidHeightIndex = 0;
+        }
+        if (untilIndex > totalItems) {
+            untilIndex = totalItems;
+        } else if (untilIndex < 0) {
+            untilIndex = 0;
+        }
+        if (untilIndex <= mLastValidHeightIndex) {
+            return mTotalHeightCache[untilIndex];
+        }
+
+        int totalItemsHeight = mTotalHeightCache[mLastValidHeightIndex];
+        for (int i = mLastValidHeightIndex; i < untilIndex; i++) {
+            totalItemsHeight = incrementTotalHeight(adapter, i, totalItemsHeight);
+            mTotalHeightCache[i + 1] = totalItemsHeight;
+        }
+        mLastValidHeightIndex = untilIndex;
+        return totalItemsHeight;
+    }
+
+    /**
+     * The current implementation assumes a linear list with every item taking up the whole row.
+     * Subclasses should override this method to account for any spanning logic
+     */
+    protected int incrementTotalHeight(Adapter adapter, int position, int heightUntilLastPos) {
+        return heightUntilLastPos + mCachedSizes.get(adapter.getItemViewType(position));
+    }
+
+    private void invalidateScrollCache() {
+        mLastValidHeightIndex = 0;
+    }
+
+    @Override
+    public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
+        super.onItemsAdded(recyclerView, positionStart, itemCount);
+        invalidateScrollCache();
+    }
+
+    @Override
+    public void onItemsChanged(RecyclerView recyclerView) {
+        super.onItemsChanged(recyclerView);
+        invalidateScrollCache();
+    }
+
+    @Override
+    public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
+        super.onItemsRemoved(recyclerView, positionStart, itemCount);
+        invalidateScrollCache();
+    }
+
+    @Override
+    public void onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount) {
+        super.onItemsMoved(recyclerView, from, to, itemCount);
+        invalidateScrollCache();
+    }
+
+    @Override
+    public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount,
+            Object payload) {
+        super.onItemsUpdated(recyclerView, positionStart, itemCount, payload);
+        invalidateScrollCache();
+    }
+}
diff --git a/src/com/android/launcher3/util/ShortcutUtil.java b/src/com/android/launcher3/util/ShortcutUtil.java
index 91cf835..79cafa0 100644
--- a/src/com/android/launcher3/util/ShortcutUtil.java
+++ b/src/com/android/launcher3/util/ShortcutUtil.java
@@ -34,7 +34,7 @@
      * Returns true when we should show depp shortcuts in shortcut menu for the item.
      */
     public static boolean supportsDeepShortcuts(ItemInfo info) {
-        return isActive(info) && isApp(info) && !WidgetsModel.GO_DISABLE_WIDGETS;
+        return isActive(info) && isApp(info) && !WidgetsModel.GO_DISABLE_SHORTCUTS;
     }
 
     /**
diff --git a/src/com/android/launcher3/util/SplitConfigurationOptions.java b/src/com/android/launcher3/util/SplitConfigurationOptions.java
index 6a336cc..88e1b22 100644
--- a/src/com/android/launcher3/util/SplitConfigurationOptions.java
+++ b/src/com/android/launcher3/util/SplitConfigurationOptions.java
@@ -16,12 +16,17 @@
 
 package com.android.launcher3.util;
 
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_ICON_MENU_SPLIT_LEFT_TOP;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_ICON_MENU_SPLIT_RIGHT_BOTTOM;
+
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
 import android.graphics.Rect;
 
 import androidx.annotation.IntDef;
 
+import com.android.launcher3.logging.StatsLogManager;
+
 import java.lang.annotation.Retention;
 
 public final class SplitConfigurationOptions {
@@ -96,7 +101,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 +129,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,11 +168,17 @@
         }
     }
 
-    public static class StagedSplitTaskPosition {
+    public static class SplitStageInfo {
         public int taskId = -1;
         @StagePosition
         public int stagePosition = STAGE_POSITION_UNDEFINED;
         @StageType
         public int stageType = STAGE_TYPE_UNDEFINED;
     }
+
+    public static StatsLogManager.EventEnum getLogEventForPosition(@StagePosition int position) {
+        return position == STAGE_POSITION_TOP_OR_LEFT
+                ? LAUNCHER_APP_ICON_MENU_SPLIT_LEFT_TOP
+                : LAUNCHER_APP_ICON_MENU_SPLIT_RIGHT_BOTTOM;
+    }
 }
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/Themes.java b/src/com/android/launcher3/util/Themes.java
index 53a584d..1728f4d 100644
--- a/src/com/android/launcher3/util/Themes.java
+++ b/src/com/android/launcher3/util/Themes.java
@@ -32,7 +32,6 @@
 
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.GraphicsUtils;
 
 /**
@@ -74,8 +73,7 @@
      * Returns true if workspace icon theming is enabled
      */
     public static boolean isThemedIconEnabled(Context context) {
-        return FeatureFlags.ENABLE_THEMED_ICONS.get()
-                && Utilities.getPrefs(context).getBoolean(KEY_THEMED_ICONS, false);
+        return Utilities.getPrefs(context).getBoolean(KEY_THEMED_ICONS, false);
     }
 
     public static String getDefaultBodyFont(Context context) {
diff --git a/src/com/android/launcher3/util/UiThreadHelper.java b/src/com/android/launcher3/util/UiThreadHelper.java
deleted file mode 100644
index 7e6711f..0000000
--- a/src/com/android/launcher3/util/UiThreadHelper.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright (C) 2017 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 static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_KEYBOARD_CLOSED;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-
-import android.annotation.SuppressLint;
-import android.app.Activity;
-import android.content.Context;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.util.Log;
-import android.view.View;
-import android.view.WindowInsets;
-import android.view.WindowInsetsController;
-import android.view.inputmethod.InputMethodManager;
-
-import com.android.launcher3.Utilities;
-import com.android.launcher3.views.ActivityContext;
-
-/**
- * Utility class for offloading some class from UI thread
- */
-public class UiThreadHelper {
-
-    private static final MainThreadInitializedObject<Handler> HANDLER =
-            new MainThreadInitializedObject<>(
-                    c -> new Handler(UI_HELPER_EXECUTOR.getLooper(), new UiCallbacks(c)));
-
-    private static final int MSG_HIDE_KEYBOARD = 1;
-    private static final int MSG_SET_ORIENTATION = 2;
-    private static final int MSG_RUN_COMMAND = 3;
-    private static final String STATS_LOGGER_KEY = "STATS_LOGGER_KEY";
-
-    @SuppressLint("NewApi")
-    public static void hideKeyboardAsync(ActivityContext activityContext, IBinder token) {
-        View root = activityContext.getDragLayer();
-
-        if (Utilities.ATLEAST_R) {
-            Preconditions.assertUIThread();
-            //  Hide keyboard with WindowInsetsController if could. In case
-            //  hideSoftInputFromWindow may get ignored by input connection being finished
-            //  when the screen is off.
-            //
-            // In addition, inside IMF, the keyboards are closed asynchronously that launcher no
-            // longer need to post to the message queue.
-            final WindowInsetsController wic = root.getWindowInsetsController();
-            WindowInsets insets = root.getRootWindowInsets();
-            boolean isImeShown = insets != null && insets.isVisible(WindowInsets.Type.ime());
-            if (wic != null && isImeShown) {
-                // this method cannot be called cross threads
-                wic.hide(WindowInsets.Type.ime());
-                activityContext.getStatsLogManager().logger()
-                        .log(LAUNCHER_ALLAPPS_KEYBOARD_CLOSED);
-                return;
-            }
-        }
-        // Since the launcher context cannot be accessed directly from callback, adding secondary
-        // message to log keyboard close event asynchronously.
-        Bundle mHideKeyboardLoggerMsg = new Bundle();
-        mHideKeyboardLoggerMsg.putParcelable(
-                STATS_LOGGER_KEY,
-                Message.obtain(
-                        HANDLER.get(root.getContext()),
-                        () -> activityContext
-                                .getStatsLogManager()
-                                .logger()
-                                .log(LAUNCHER_ALLAPPS_KEYBOARD_CLOSED)
-                )
-        );
-
-        Message mHideKeyboardMsg = Message.obtain(HANDLER.get(root.getContext()), MSG_HIDE_KEYBOARD,
-                token);
-        mHideKeyboardMsg.setData(mHideKeyboardLoggerMsg);
-        mHideKeyboardMsg.sendToTarget();
-    }
-
-    public static void setOrientationAsync(Activity activity, int orientation) {
-        Message.obtain(HANDLER.get(activity), MSG_SET_ORIENTATION, orientation, 0, activity)
-                .sendToTarget();
-    }
-
-    public static void setBackButtonAlphaAsync(Context context, AsyncCommand command, float alpha,
-            boolean animate) {
-        runAsyncCommand(context, command, Float.floatToIntBits(alpha), animate ? 1 : 0);
-    }
-
-    public static void runAsyncCommand(Context context, AsyncCommand command, int arg1, int arg2) {
-        Message.obtain(HANDLER.get(context), MSG_RUN_COMMAND, arg1, arg2, command).sendToTarget();
-    }
-
-    private static class UiCallbacks implements Handler.Callback {
-
-        private final Context mContext;
-        private final InputMethodManager mIMM;
-
-        UiCallbacks(Context context) {
-            mContext = context;
-            mIMM = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
-        }
-
-        @Override
-        public boolean handleMessage(Message message) {
-            switch (message.what) {
-                case MSG_HIDE_KEYBOARD:
-                    if (mIMM.hideSoftInputFromWindow((IBinder) message.obj, 0)) {
-                        // log keyboard close event only when keyboard is actually closed
-                        ((Message) message.getData().getParcelable(STATS_LOGGER_KEY))
-                                .sendToTarget();
-                    }
-                    return true;
-                case MSG_SET_ORIENTATION:
-                    ((Activity) message.obj).setRequestedOrientation(message.arg1);
-                    return true;
-                case MSG_RUN_COMMAND:
-                    ((AsyncCommand) message.obj).execute(mContext, message.arg1, message.arg2);
-                    return true;
-            }
-            return false;
-        }
-    }
-
-    public interface AsyncCommand {
-        void execute(Context proxy, int arg1, int arg2);
-    }
-}
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..fb2ae73 100644
--- a/src/com/android/launcher3/util/window/WindowManagerProxy.java
+++ b/src/com/android/launcher3/util/window/WindowManagerProxy.java
@@ -16,16 +16,16 @@
 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;
-import static com.android.launcher3.ResourceUtils.NAVBAR_HEIGHT_LANDSCAPE;
-import static com.android.launcher3.ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE;
-import static com.android.launcher3.ResourceUtils.STATUS_BAR_HEIGHT;
-import static com.android.launcher3.ResourceUtils.STATUS_BAR_HEIGHT_LANDSCAPE;
-import static com.android.launcher3.ResourceUtils.STATUS_BAR_HEIGHT_PORTRAIT;
 import static com.android.launcher3.Utilities.dpiFromPx;
+import static com.android.launcher3.testing.shared.ResourceUtils.INVALID_RESOURCE_HANDLE;
+import static com.android.launcher3.testing.shared.ResourceUtils.NAVBAR_HEIGHT;
+import static com.android.launcher3.testing.shared.ResourceUtils.NAVBAR_HEIGHT_LANDSCAPE;
+import static com.android.launcher3.testing.shared.ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE;
+import static com.android.launcher3.testing.shared.ResourceUtils.NAV_BAR_INTERACTION_MODE_RES_NAME;
+import static com.android.launcher3.testing.shared.ResourceUtils.STATUS_BAR_HEIGHT;
+import static com.android.launcher3.testing.shared.ResourceUtils.STATUS_BAR_HEIGHT_LANDSCAPE;
+import static com.android.launcher3.testing.shared.ResourceUtils.STATUS_BAR_HEIGHT_PORTRAIT;
 import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
 import static com.android.launcher3.util.RotationUtils.deltaRotation;
 import static com.android.launcher3.util.RotationUtils.rotateRect;
@@ -41,7 +41,7 @@
 import android.hardware.display.DisplayManager;
 import android.os.Build;
 import android.util.ArrayMap;
-import android.util.Pair;
+import android.util.Log;
 import android.view.Display;
 import android.view.DisplayCutout;
 import android.view.Surface;
@@ -50,9 +50,10 @@
 import android.view.WindowMetrics;
 
 import com.android.launcher3.R;
-import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.testing.shared.ResourceUtils;
 import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.NavigationMode;
 import com.android.launcher3.util.ResourceBasedOverride;
 import com.android.launcher3.util.WindowBounds;
 
@@ -61,6 +62,7 @@
  */
 public class WindowManagerProxy implements ResourceBasedOverride {
 
+    private static final String TAG = "WindowManagerProxy";
     public static final int MIN_TABLET_WIDTH = 600;
 
     public static final MainThreadInitializedObject<WindowManagerProxy> INSTANCE =
@@ -88,20 +90,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 +103,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 +115,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 +161,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 +191,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 +237,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 +293,78 @@
      * 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();
+        return displayInfoContext.getSystemService(DisplayManager.class).getDisplay(
+                DEFAULT_DISPLAY);
     }
 
     /**
-     * Returns all currently valid logical displays.
+     * Returns the current navigation mode from resource.
      */
-    protected Display[] getDisplays(Context context) {
-        return context.getSystemService(DisplayManager.class).getDisplays();
+    public NavigationMode getNavigationMode(Context context) {
+        int modeInt = ResourceUtils.getIntegerByName(NAV_BAR_INTERACTION_MODE_RES_NAME,
+                context.getResources(), INVALID_RESOURCE_HANDLE);
+
+        if (modeInt == INVALID_RESOURCE_HANDLE) {
+            Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
+        } else {
+            for (NavigationMode m : NavigationMode.values()) {
+                if (m.resValue == modeInt) {
+                    return m;
+                }
+            }
+        }
+        return Utilities.ATLEAST_S ? NavigationMode.NO_BUTTON :
+                NavigationMode.THREE_BUTTONS;
     }
 }
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index 93078e4..dd5b22e 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -15,16 +15,26 @@
  */
 package com.android.launcher3.views;
 
+import static com.android.launcher3.logging.KeyboardStateManager.KeyboardState.HIDE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_KEYBOARD_CLOSED;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.graphics.Rect;
+import android.os.IBinder;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.View.AccessibilityDelegate;
+import android.view.WindowInsets;
+import android.view.WindowInsetsController;
+import android.view.inputmethod.InputMethodManager;
 
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.ActivityAllAppsContainerView;
 import com.android.launcher3.allapps.search.SearchAdapterProvider;
 import com.android.launcher3.dot.DotInfo;
@@ -36,6 +46,7 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.popup.PopupDataProvider;
 import com.android.launcher3.util.OnboardingPrefs;
+import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.ViewCache;
 
 /**
@@ -141,6 +152,7 @@
     default void applyOverwritesToLogItem(LauncherAtom.ItemInfo.Builder itemInfoBuilder) { }
 
     /** Onboarding preferences for any onboarding data within this context. */
+    @Nullable
     default OnboardingPrefs<?> getOnboardingPrefs() {
         return null;
     }
@@ -201,4 +213,47 @@
             ActivityAllAppsContainerView<?> appsView) {
         return null;
     }
+
+    /**
+     * Hides the keyboard if it is visible
+     */
+    default void hideKeyboard() {
+        View root = getDragLayer();
+        if (root == null) {
+            return;
+        }
+        if (Utilities.ATLEAST_R) {
+            Preconditions.assertUIThread();
+            //  Hide keyboard with WindowInsetsController if could. In case
+            //  hideSoftInputFromWindow may get ignored by input connection being finished
+            //  when the screen is off.
+            //
+            // In addition, inside IMF, the keyboards are closed asynchronously that launcher no
+            // longer need to post to the message queue.
+            final WindowInsetsController wic = root.getWindowInsetsController();
+            WindowInsets insets = root.getRootWindowInsets();
+            boolean isImeShown = insets != null && insets.isVisible(WindowInsets.Type.ime());
+            if (wic != null && isImeShown) {
+                StatsLogManager slm  = getStatsLogManager();
+                slm.keyboardStateManager().setKeyboardState(HIDE);
+
+                // this method cannot be called cross threads
+                wic.hide(WindowInsets.Type.ime());
+                slm.logger().log(LAUNCHER_ALLAPPS_KEYBOARD_CLOSED);
+                return;
+            }
+        }
+
+        InputMethodManager imm = root.getContext().getSystemService(InputMethodManager.class);
+        IBinder token = root.getWindowToken();
+        if (imm != null && token != null) {
+            UI_HELPER_EXECUTOR.execute(() -> {
+                if (imm.hideSoftInputFromWindow(token, 0)) {
+                    // log keyboard close event only when keyboard is actually closed
+                    MAIN_EXECUTOR.execute(() ->
+                            getStatsLogManager().logger().log(LAUNCHER_ALLAPPS_KEYBOARD_CLOSED));
+                }
+            });
+        }
+    }
 }
diff --git a/src/com/android/launcher3/views/AllAppsButton.java b/src/com/android/launcher3/views/AllAppsButton.java
index b1e69c7..ab8e5db 100644
--- a/src/com/android/launcher3/views/AllAppsButton.java
+++ b/src/com/android/launcher3/views/AllAppsButton.java
@@ -45,5 +45,6 @@
         Bitmap bitmap = LauncherAppState.getInstance(context).getIconCache().getIconFactory()
                 .createScaledBitmapWithShadow(theme.getDrawable(R.drawable.ic_all_apps_button));
         setIcon(new FastBitmapDrawable(bitmap));
+        setContentDescription(context.getString(R.string.all_apps_button_label));
     }
 }
diff --git a/src/com/android/launcher3/views/AppLauncher.java b/src/com/android/launcher3/views/AppLauncher.java
index 19e66ab..dc07e45 100644
--- a/src/com/android/launcher3/views/AppLauncher.java
+++ b/src/com/android/launcher3/views/AppLauncher.java
@@ -16,7 +16,7 @@
 package com.android.launcher3.views;
 
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
-import static com.android.launcher3.model.WidgetsModel.GO_DISABLE_WIDGETS;
+import static com.android.launcher3.model.WidgetsModel.GO_DISABLE_SHORTCUTS;
 
 import android.app.ActivityOptions;
 import android.content.ActivityNotFoundException;
@@ -190,7 +190,7 @@
      */
     default void startShortcut(String packageName, String id, Rect sourceBounds,
             Bundle startActivityOptions, UserHandle user) {
-        if (GO_DISABLE_WIDGETS) {
+        if (GO_DISABLE_SHORTCUTS) {
             return;
         }
         try {
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index f553fb4..800b1f6 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -22,13 +22,11 @@
 
 import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
 
-import android.annotation.TargetApi;
 import android.app.WallpaperManager;
 import android.content.Context;
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.graphics.RectF;
-import android.os.Build;
 import android.util.AttributeSet;
 import android.util.Property;
 import android.view.MotionEvent;
@@ -550,18 +548,24 @@
     }
 
     @Override
-    @TargetApi(Build.VERSION_CODES.Q)
     public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
         if (Utilities.ATLEAST_Q) {
             Insets gestureInsets = insets.getMandatorySystemGestureInsets();
             int gestureInsetBottom = gestureInsets.bottom;
+            Insets imeInset = Utilities.ATLEAST_R
+                    ? insets.getInsets(WindowInsets.Type.ime())
+                    : Insets.NONE;
             DeviceProfile dp = mActivity.getDeviceProfile();
             if (dp.isTaskbarPresent) {
                 // Ignore taskbar gesture insets to avoid interfering with TouchControllers.
                 gestureInsetBottom = Math.max(0, gestureInsetBottom - dp.taskbarSize);
             }
-            mSystemGestureRegion.set(gestureInsets.left, gestureInsets.top,
-                    gestureInsets.right, gestureInsetBottom);
+            mSystemGestureRegion.set(
+                    Math.max(gestureInsets.left, imeInset.left),
+                    Math.max(gestureInsets.top, imeInset.top),
+                    Math.max(gestureInsets.right, imeInset.right),
+                    Math.max(gestureInsetBottom, imeInset.bottom)
+            );
         }
         return super.dispatchApplyWindowInsets(insets);
     }
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index efc83eb..55af622 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.views;
 
+import static android.view.Gravity.LEFT;
+
 import static com.android.launcher3.Utilities.getBadge;
 import static com.android.launcher3.Utilities.getFullDrawable;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -181,8 +183,10 @@
         updatePosition(positionOut, lp);
         setLayoutParams(lp);
 
-        mClipIconView.setLayoutParams(new FrameLayout.LayoutParams(lp.width, lp.height));
-        mBtvDrawable.setLayoutParams(new FrameLayout.LayoutParams(lp.width, lp.height));
+        // For code simplicity, we always layout the child views using Gravity.LEFT
+        // and manually handle RTL for FloatingIconView when positioning it on the screen.
+        mClipIconView.setLayoutParams(new FrameLayout.LayoutParams(lp.width, lp.height, LEFT));
+        mBtvDrawable.setLayoutParams(new FrameLayout.LayoutParams(lp.width, lp.height, LEFT));
     }
 
     private void updatePosition(RectF pos, InsettableFrameLayout.LayoutParams lp) {
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index 2a9a8a5..d301925 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -50,7 +50,7 @@
 import com.android.launcher3.popup.ArrowPopup;
 import com.android.launcher3.shortcuts.DeepShortcutView;
 import com.android.launcher3.testing.TestLogging;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.widget.picker.WidgetsFullSheet;
 
 import java.util.ArrayList;
diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
index 11ca130..3af2e3c 100644
--- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java
+++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
@@ -20,8 +20,6 @@
 
 import static androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE;
 
-import static com.android.launcher3.util.UiThreadHelper.hideKeyboardAsync;
-
 import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.content.res.Resources;
@@ -121,7 +119,6 @@
     // prevent jumping, this offset is applied as the user scrolls.
     protected int mTouchOffsetY;
     protected int mThumbOffsetY;
-    protected int mRvOffsetY;
 
     // Fast scroller popup
     private TextView mPopupView;
@@ -174,6 +171,11 @@
         ta.recycle();
     }
 
+    /** @return whether there is a RecyclerView bound to this scroller. */
+    public boolean hasRecyclerView() {
+        return mRv != null;
+    }
+
     public void setRecyclerView(FastScrollRecyclerView rv, TextView popupView) {
         if (mRv != null && mOnScrollListener != null) {
             mRv.removeOnScrollListener(mOnScrollListener);
@@ -204,16 +206,11 @@
 
     public void setThumbOffsetY(int y) {
         if (mThumbOffsetY == y) {
-            int rvCurrentOffsetY = mRv.getCurrentScrollY();
-            if (mRvOffsetY != rvCurrentOffsetY) {
-                mRvOffsetY = mRv.getCurrentScrollY();
-            }
             return;
         }
         updatePopupY(y);
         mThumbOffsetY = y;
         invalidate();
-        mRvOffsetY = mRv.getCurrentScrollY();
     }
 
     public int getThumbOffsetY() {
@@ -308,7 +305,7 @@
     }
 
     private void calcTouchOffsetAndPrepToFastScroll(int downY, int lastY) {
-        hideKeyboardAsync(ActivityContext.lookupContext(getContext()), getWindowToken());
+        ActivityContext.lookupContext(getContext()).hideKeyboard();
         mIsDragging = true;
         if (mCanThumbDetach) {
             mIsThumbDetached = true;
diff --git a/src/com/android/launcher3/views/StickyHeaderLayout.java b/src/com/android/launcher3/views/StickyHeaderLayout.java
new file mode 100644
index 0000000..d6481a9
--- /dev/null
+++ b/src/com/android/launcher3/views/StickyHeaderLayout.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.views;
+
+import static android.view.View.MeasureSpec.EXACTLY;
+import static android.view.View.MeasureSpec.makeMeasureSpec;
+
+import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.util.FloatProperty;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.launcher3.R;
+
+/**
+ * A {@link LinearLayout} container which allows scrolling parts of its content based on the
+ * scroll of a different view. Views which are marked as sticky are not scrolled, giving the
+ * illusion of a sticky header.
+ */
+public class StickyHeaderLayout extends LinearLayout implements
+        RecyclerView.OnChildAttachStateChangeListener {
+
+    private static final FloatProperty<StickyHeaderLayout> SCROLL_OFFSET =
+            new FloatProperty<StickyHeaderLayout>("scrollAnimOffset") {
+                @Override
+                public void setValue(StickyHeaderLayout view, float offset) {
+                    view.mScrollOffset = offset;
+                    view.updateHeaderScroll();
+                }
+
+                @Override
+                public Float get(StickyHeaderLayout view) {
+                    return view.mScrollOffset;
+                }
+            };
+
+    private static final MotionEventProxyMethod INTERCEPT_PROXY = ViewGroup::onInterceptTouchEvent;
+    private static final MotionEventProxyMethod TOUCH_PROXY = ViewGroup::onTouchEvent;
+
+    private RecyclerView mCurrentRecyclerView;
+    private EmptySpaceView mCurrentEmptySpaceView;
+
+    private float mLastScroll = 0;
+    private float mScrollOffset = 0;
+    private Animator mOffsetAnimator;
+
+    private boolean mShouldForwardToRecyclerView = false;
+    private int mHeaderHeight;
+
+    public StickyHeaderLayout(Context context) {
+        this(context, /* attrs= */ null);
+    }
+
+    public StickyHeaderLayout(Context context, AttributeSet attrs) {
+        this(context, attrs, /* defStyleAttr= */ 0);
+    }
+
+    public StickyHeaderLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, /* defStyleRes= */ 0);
+    }
+
+    public StickyHeaderLayout(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    /**
+     * Sets the recycler view, this sticky header should track
+     */
+    public void setCurrentRecyclerView(RecyclerView currentRecyclerView) {
+        boolean animateReset = mCurrentRecyclerView != null;
+        if (mCurrentRecyclerView != null) {
+            mCurrentRecyclerView.removeOnChildAttachStateChangeListener(this);
+        }
+        mCurrentRecyclerView = currentRecyclerView;
+        mCurrentRecyclerView.addOnChildAttachStateChangeListener(this);
+        findCurrentEmptyView();
+        reset(animateReset);
+    }
+
+    public int getHeaderHeight() {
+        return mHeaderHeight;
+    }
+
+    private void updateHeaderScroll() {
+        mLastScroll = getCurrentScroll();
+        int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            View child = getChildAt(i);
+            MyLayoutParams lp = (MyLayoutParams) child.getLayoutParams();
+            child.setTranslationY(Math.max(mLastScroll, lp.scrollLimit));
+        }
+    }
+
+    private float getCurrentScroll() {
+        return mScrollOffset + (mCurrentEmptySpaceView == null ? 0 : mCurrentEmptySpaceView.getY());
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        mHeaderHeight = getMeasuredHeight();
+        if (mCurrentEmptySpaceView != null) {
+            mCurrentEmptySpaceView.setFixedHeight(mHeaderHeight);
+        }
+    }
+
+    /** Resets any previous view translation. */
+    public void reset(boolean animate) {
+        if (mOffsetAnimator != null) {
+            mOffsetAnimator.cancel();
+            mOffsetAnimator = null;
+        }
+
+        mScrollOffset = 0;
+        if (!animate) {
+            updateHeaderScroll();
+        } else {
+            float startValue = mLastScroll - getCurrentScroll();
+            mOffsetAnimator = ObjectAnimator.ofFloat(this, SCROLL_OFFSET, startValue, 0);
+            mOffsetAnimator.addListener(forEndCallback(() -> mOffsetAnimator = null));
+            mOffsetAnimator.start();
+        }
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent event) {
+        return (mShouldForwardToRecyclerView = proxyMotionEvent(event, INTERCEPT_PROXY))
+                || super.onInterceptTouchEvent(event);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        return mShouldForwardToRecyclerView && proxyMotionEvent(event, TOUCH_PROXY)
+                || super.onTouchEvent(event);
+    }
+
+    private boolean proxyMotionEvent(MotionEvent event, MotionEventProxyMethod method) {
+        float dx = mCurrentRecyclerView.getLeft() - getLeft();
+        float dy = mCurrentRecyclerView.getTop() - getTop();
+        event.offsetLocation(dx, dy);
+        try {
+            return method.proxyEvent(mCurrentRecyclerView, event);
+        } finally {
+            event.offsetLocation(-dx, -dy);
+        }
+    }
+
+    @Override
+    public void onChildViewAttachedToWindow(@NonNull View view) {
+        if (view instanceof EmptySpaceView) {
+            findCurrentEmptyView();
+        }
+    }
+
+    @Override
+    public void onChildViewDetachedFromWindow(@NonNull View view) {
+        if (view == mCurrentEmptySpaceView) {
+            findCurrentEmptyView();
+        }
+    }
+
+    private void findCurrentEmptyView() {
+        if (mCurrentEmptySpaceView != null) {
+            mCurrentEmptySpaceView.setOnYChangeCallback(null);
+            mCurrentEmptySpaceView = null;
+        }
+        int childCount = mCurrentRecyclerView.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View view = mCurrentRecyclerView.getChildAt(i);
+            if (view instanceof EmptySpaceView) {
+                mCurrentEmptySpaceView = (EmptySpaceView) view;
+                mCurrentEmptySpaceView.setFixedHeight(getHeaderHeight());
+                mCurrentEmptySpaceView.setOnYChangeCallback(this::updateHeaderScroll);
+                return;
+            }
+        }
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+
+        // Update various stick parameters
+        int count = getChildCount();
+        int stickyHeaderHeight = 0;
+        for (int i = 0; i < count; i++) {
+            View v = getChildAt(i);
+            MyLayoutParams lp = (MyLayoutParams) v.getLayoutParams();
+            if (lp.sticky) {
+                lp.scrollLimit = -v.getTop() + stickyHeaderHeight;
+                stickyHeaderHeight += v.getHeight();
+            } else {
+                lp.scrollLimit = Integer.MIN_VALUE;
+            }
+        }
+        updateHeaderScroll();
+    }
+
+    @Override
+    protected LayoutParams generateDefaultLayoutParams() {
+        return new MyLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+    }
+
+    @Override
+    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
+        return new MyLayoutParams(lp.width, lp.height);
+    }
+
+    @Override
+    public LayoutParams generateLayoutParams(AttributeSet attrs) {
+        return new MyLayoutParams(getContext(), attrs);
+    }
+
+    @Override
+    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+        return p instanceof MyLayoutParams;
+    }
+
+    private static class MyLayoutParams extends LayoutParams {
+
+        public final boolean sticky;
+        public int scrollLimit;
+
+        MyLayoutParams(int width, int height) {
+            super(width, height);
+            sticky = false;
+        }
+
+        MyLayoutParams(Context c, AttributeSet attrs) {
+            super(c, attrs);
+            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.StickyScroller_Layout);
+            sticky = a.getBoolean(R.styleable.StickyScroller_Layout_layout_sticky, false);
+            a.recycle();
+        }
+    }
+
+    private interface MotionEventProxyMethod {
+
+        boolean proxyEvent(ViewGroup view, MotionEvent event);
+    }
+
+    /**
+     * Empty view which allows listening for 'Y' changes
+     */
+    public static class EmptySpaceView extends View {
+
+        private Runnable mOnYChangeCallback;
+        private int mHeight = 0;
+
+        public EmptySpaceView(Context context) {
+            super(context);
+            animate().setUpdateListener(v -> notifyYChanged());
+        }
+
+        /**
+         * Sets the height for the empty view
+         * @return true if the height changed, false otherwise
+         */
+        public boolean setFixedHeight(int height) {
+            if (mHeight != height) {
+                mHeight = height;
+                requestLayout();
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+            super.onMeasure(widthMeasureSpec, makeMeasureSpec(mHeight, EXACTLY));
+        }
+
+        public void setOnYChangeCallback(Runnable callback) {
+            mOnYChangeCallback = callback;
+        }
+
+        @Override
+        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+            super.onLayout(changed, left, top, right, bottom);
+            notifyYChanged();
+        }
+
+        @Override
+        public void offsetTopAndBottom(int offset) {
+            super.offsetTopAndBottom(offset);
+            notifyYChanged();
+        }
+
+        @Override
+        public void setTranslationY(float translationY) {
+            super.setTranslationY(translationY);
+            notifyYChanged();
+        }
+
+        private void notifyYChanged() {
+            if (mOnYChangeCallback != null) {
+                mOnYChangeCallback.run();
+            }
+        }
+    }
+}
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index 8962c4f..2ac1e94 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.widget;
 
+import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
+
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Paint;
@@ -26,6 +28,7 @@
 import android.view.View.OnClickListener;
 import android.view.View.OnLongClickListener;
 import android.view.WindowInsets;
+import android.view.animation.Interpolator;
 import android.widget.Toast;
 
 import androidx.annotation.GuardedBy;
@@ -42,7 +45,7 @@
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.popup.PopupDataProvider;
 import com.android.launcher3.testing.TestLogging;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.Themes;
@@ -246,6 +249,12 @@
         return true;
     }
 
+    @Override
+    protected Interpolator getIdleInterpolator() {
+        return mActivityContext.getDeviceProfile().isTablet
+                ? EMPHASIZED : super.getIdleInterpolator();
+    }
+
     //
     // Drag related handling methods that implement {@link DragSource} interface.
     //
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHost.java b/src/com/android/launcher3/widget/LauncherAppWidgetHost.java
index fe83f3f..3e80699 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,10 +38,11 @@
 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;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.widget.custom.CustomWidgetManager;
 
 import java.util.ArrayList;
@@ -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,17 @@
     @Override
     public void clearViews() {
         super.clearViews();
+        if (FeatureFlags.ENABLE_CACHED_WIDGET.get()) {
+            // First, we clear any previously cached content from existing widgets
+            mCachedRemoteViews.clear();
+            mDeferredViews.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/PendingAddWidgetInfo.java b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
index 470a800..ccf4b2e 100644
--- a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
+++ b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
@@ -19,6 +19,9 @@
 import android.content.Context;
 import android.os.Bundle;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.PendingAddItemInfo;
 import com.android.launcher3.logger.LauncherAtom;
@@ -66,8 +69,9 @@
         return WidgetSizes.getWidgetSizeOptions(context, componentName, spanX, spanY);
     }
 
+    @NonNull
     @Override
-    public LauncherAtom.ItemInfo buildProto(FolderInfo folderInfo) {
+    public LauncherAtom.ItemInfo buildProto(@Nullable FolderInfo folderInfo) {
         LauncherAtom.ItemInfo info = super.buildProto(folderInfo);
         return info.toBuilder()
                 .addItemAttributes(LauncherAppWidgetInfo.getAttribute(sourceContainer))
diff --git a/src/com/android/launcher3/widget/PendingItemDragHelper.java b/src/com/android/launcher3/widget/PendingItemDragHelper.java
index 46c0b99..c8d528b 100644
--- a/src/com/android/launcher3/widget/PendingItemDragHelper.java
+++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java
@@ -42,7 +42,7 @@
 import com.android.launcher3.icons.FastBitmapDrawable;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.icons.RoundDrawableWrapper;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.widget.dragndrop.AppWidgetHostViewDragListener;
 import com.android.launcher3.widget.util.WidgetSizes;
 
diff --git a/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java b/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java
deleted file mode 100644
index 716dcf3..0000000
--- a/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.widget.picker;
-
-import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
-
-import android.animation.Animator;
-import android.animation.ObjectAnimator;
-import android.util.FloatProperty;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.android.launcher3.R;
-import com.android.launcher3.widget.picker.WidgetsSpaceViewHolderBinder.EmptySpaceView;
-import com.android.launcher3.widget.picker.search.WidgetsSearchBar;
-
-/**
- * A controller which measures & updates {@link WidgetsFullSheet}'s views padding, margin and
- * vertical displacement upon scrolling.
- */
-final class SearchAndRecommendationsScrollController implements
-        RecyclerView.OnChildAttachStateChangeListener {
-
-    private static final FloatProperty<SearchAndRecommendationsScrollController> SCROLL_OFFSET =
-            new FloatProperty<SearchAndRecommendationsScrollController>("scrollAnimOffset") {
-        @Override
-        public void setValue(SearchAndRecommendationsScrollController controller, float offset) {
-            controller.mScrollOffset = offset;
-            controller.updateHeaderScroll();
-        }
-
-        @Override
-        public Float get(SearchAndRecommendationsScrollController controller) {
-            return controller.mScrollOffset;
-        }
-    };
-
-    private static final MotionEventProxyMethod INTERCEPT_PROXY = ViewGroup::onInterceptTouchEvent;
-    private static final MotionEventProxyMethod TOUCH_PROXY = ViewGroup::onTouchEvent;
-
-    final SearchAndRecommendationsView mContainer;
-    final View mSearchBarContainer;
-    final WidgetsSearchBar mSearchBar;
-    final TextView mHeaderTitle;
-    final WidgetsRecommendationTableLayout mRecommendedWidgetsTable;
-    @Nullable final View mTabBar;
-
-    private WidgetsRecyclerView mCurrentRecyclerView;
-    private EmptySpaceView mCurrentEmptySpaceView;
-
-    private float mLastScroll = 0;
-    private float mScrollOffset = 0;
-    private Animator mOffsetAnimator;
-
-    private boolean mShouldForwardToRecyclerView = false;
-
-    private int mHeaderHeight;
-
-    SearchAndRecommendationsScrollController(
-            SearchAndRecommendationsView searchAndRecommendationContainer) {
-        mContainer = searchAndRecommendationContainer;
-        mSearchBarContainer = mContainer.findViewById(R.id.search_bar_container);
-        mSearchBar = mContainer.findViewById(R.id.widgets_search_bar);
-        mHeaderTitle = mContainer.findViewById(R.id.title);
-        mRecommendedWidgetsTable = mContainer.findViewById(R.id.recommended_widget_table);
-        mTabBar = mContainer.findViewById(R.id.tabs);
-
-        mContainer.setSearchAndRecommendationScrollController(this);
-    }
-
-    public void setCurrentRecyclerView(WidgetsRecyclerView currentRecyclerView) {
-        boolean animateReset = mCurrentRecyclerView != null;
-        if (mCurrentRecyclerView != null) {
-            mCurrentRecyclerView.removeOnChildAttachStateChangeListener(this);
-        }
-        mCurrentRecyclerView = currentRecyclerView;
-        mCurrentRecyclerView.addOnChildAttachStateChangeListener(this);
-        findCurrentEmptyView();
-        reset(animateReset);
-    }
-
-    public int getHeaderHeight() {
-        return mHeaderHeight;
-    }
-
-    private void updateHeaderScroll() {
-        mLastScroll = getCurrentScroll();
-        mHeaderTitle.setTranslationY(mLastScroll);
-        mRecommendedWidgetsTable.setTranslationY(mLastScroll);
-
-        float searchYDisplacement = Math.max(mLastScroll, -mSearchBarContainer.getTop());
-        mSearchBarContainer.setTranslationY(searchYDisplacement);
-
-        if (mTabBar != null) {
-            float tabsDisplacement = Math.max(mLastScroll, -mTabBar.getTop()
-                    + mSearchBarContainer.getHeight());
-            mTabBar.setTranslationY(tabsDisplacement);
-        }
-    }
-
-    private float getCurrentScroll() {
-        return mScrollOffset + (mCurrentEmptySpaceView == null ? 0 : mCurrentEmptySpaceView.getY());
-    }
-
-    /**
-     * Updates the scrollable header height
-     *
-     * @return {@code true} if the header height or dependent property changed.
-     */
-    public boolean updateHeaderHeight() {
-        boolean hasSizeUpdated = false;
-
-        int headerHeight = mContainer.getMeasuredHeight();
-        if (headerHeight != mHeaderHeight) {
-            mHeaderHeight = headerHeight;
-            hasSizeUpdated = true;
-        }
-
-        if (mCurrentEmptySpaceView != null
-                && mCurrentEmptySpaceView.setFixedHeight(mHeaderHeight)) {
-            hasSizeUpdated = true;
-        }
-        return hasSizeUpdated;
-    }
-
-    /** Resets any previous view translation. */
-    public void reset(boolean animate) {
-        if (mOffsetAnimator != null) {
-            mOffsetAnimator.cancel();
-            mOffsetAnimator = null;
-        }
-
-        mScrollOffset = 0;
-        if (!animate) {
-            updateHeaderScroll();
-        } else {
-            float startValue = mLastScroll - getCurrentScroll();
-            mOffsetAnimator = ObjectAnimator.ofFloat(this, SCROLL_OFFSET, startValue, 0);
-            mOffsetAnimator.addListener(forEndCallback(() -> mOffsetAnimator = null));
-            mOffsetAnimator.start();
-        }
-    }
-
-    /**
-     * Returns {@code true} if a touch event should be intercepted by this controller.
-     */
-    public boolean onInterceptTouchEvent(MotionEvent event) {
-        return (mShouldForwardToRecyclerView = proxyMotionEvent(event, INTERCEPT_PROXY));
-    }
-
-    /**
-     * Returns {@code true} if this controller has intercepted and consumed a touch event.
-     */
-    public boolean onTouchEvent(MotionEvent event) {
-        return mShouldForwardToRecyclerView && proxyMotionEvent(event, TOUCH_PROXY);
-    }
-
-    private boolean proxyMotionEvent(MotionEvent event, MotionEventProxyMethod method) {
-        float dx = mCurrentRecyclerView.getLeft() - mContainer.getLeft();
-        float dy = mCurrentRecyclerView.getTop() - mContainer.getTop();
-        event.offsetLocation(dx, dy);
-        try {
-            return method.proxyEvent(mCurrentRecyclerView, event);
-        } finally {
-            event.offsetLocation(-dx, -dy);
-        }
-    }
-
-    @Override
-    public void onChildViewAttachedToWindow(@NonNull View view) {
-        if (view instanceof EmptySpaceView) {
-            findCurrentEmptyView();
-        }
-    }
-
-    @Override
-    public void onChildViewDetachedFromWindow(@NonNull View view) {
-        if (view == mCurrentEmptySpaceView) {
-            findCurrentEmptyView();
-        }
-    }
-
-    private void findCurrentEmptyView() {
-        if (mCurrentEmptySpaceView != null) {
-            mCurrentEmptySpaceView.setOnYChangeCallback(null);
-            mCurrentEmptySpaceView = null;
-        }
-        int childCount = mCurrentRecyclerView.getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            View view = mCurrentRecyclerView.getChildAt(i);
-            if (view instanceof EmptySpaceView) {
-                mCurrentEmptySpaceView = (EmptySpaceView) view;
-                mCurrentEmptySpaceView.setFixedHeight(getHeaderHeight());
-                mCurrentEmptySpaceView.setOnYChangeCallback(this::updateHeaderScroll);
-                return;
-            }
-        }
-    }
-
-    private interface MotionEventProxyMethod {
-
-        boolean proxyEvent(ViewGroup view, MotionEvent event);
-    }
-}
diff --git a/src/com/android/launcher3/widget/picker/SearchAndRecommendationsView.java b/src/com/android/launcher3/widget/picker/SearchAndRecommendationsView.java
deleted file mode 100644
index 0d7d2b5..0000000
--- a/src/com/android/launcher3/widget/picker/SearchAndRecommendationsView.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.widget.picker;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.widget.LinearLayout;
-
-/**
- * A {@link LinearLayout} container for holding search and widgets recommendation.
- *
- * <p>This class intercepts touch events and dispatch them to the right view.
- */
-public class SearchAndRecommendationsView extends LinearLayout {
-    private SearchAndRecommendationsScrollController mController;
-
-    public SearchAndRecommendationsView(Context context) {
-        this(context, /* attrs= */ null);
-    }
-
-    public SearchAndRecommendationsView(Context context, AttributeSet attrs) {
-        this(context, attrs, /* defStyleAttr= */ 0);
-    }
-
-    public SearchAndRecommendationsView(Context context, AttributeSet attrs, int defStyleAttr) {
-        this(context, attrs, defStyleAttr, /* defStyleRes= */ 0);
-    }
-
-    public SearchAndRecommendationsView(Context context, AttributeSet attrs, int defStyleAttr,
-            int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-    }
-
-    public void setSearchAndRecommendationScrollController(
-            SearchAndRecommendationsScrollController controller) {
-        mController = controller;
-    }
-
-    @Override
-    public boolean onInterceptTouchEvent(MotionEvent event) {
-        return mController.onInterceptTouchEvent(event) || super.onInterceptTouchEvent(event);
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent event) {
-        return mController.onTouchEvent(event) || super.onTouchEvent(event);
-    }
-}
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/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index a49cdc0..da8e25c 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -19,7 +19,7 @@
 
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGETSTRAY_SEARCHED;
-import static com.android.launcher3.testing.TestProtocol.NORMAL_STATE_ORDINAL;
+import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -62,11 +62,13 @@
 import com.android.launcher3.views.ArrowTipView;
 import com.android.launcher3.views.RecyclerViewFastScroller;
 import com.android.launcher3.views.SpringRelativeLayout;
+import com.android.launcher3.views.StickyHeaderLayout;
 import com.android.launcher3.views.WidgetsEduView;
 import com.android.launcher3.widget.BaseWidgetSheet;
 import com.android.launcher3.widget.LauncherAppWidgetHost.ProviderChangedListener;
 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
 import com.android.launcher3.widget.picker.search.SearchModeListener;
+import com.android.launcher3.widget.picker.search.WidgetsSearchBar;
 import com.android.launcher3.widget.util.WidgetsTableUtils;
 import com.android.launcher3.workprofile.PersonalWorkPagedView;
 import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePageChangedListener;
@@ -83,7 +85,6 @@
         implements ProviderChangedListener, OnActivePageChangedListener,
         WidgetsRecyclerView.HeaderViewDimensionsProvider, SearchModeListener {
 
-    private static final long DEFAULT_OPEN_DURATION = 267;
     private static final long FADE_IN_DURATION = 150;
     private static final long EDUCATION_TIP_DELAY_MS = 200;
     private static final long EDUCATION_DIALOG_DELAY_MS = 500;
@@ -161,7 +162,13 @@
     private boolean mIsNoWidgetsViewNeeded;
     private int mMaxSpansPerRow = DEFAULT_MAX_HORIZONTAL_SPANS;
     private TextView mNoWidgetsView;
-    private SearchAndRecommendationsScrollController mSearchScrollController;
+
+    private StickyHeaderLayout mSearchScrollView;
+    private WidgetsRecommendationTableLayout mRecommendedWidgetsTable;
+    private View mTabBar;
+    private View mSearchBarContainer;
+    private WidgetsSearchBar mSearchBar;
+    private TextView mHeaderTitle;
 
     public WidgetsFullSheet(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
@@ -214,17 +221,23 @@
         }
 
         mNoWidgetsView = findViewById(R.id.no_widgets_text);
-        mSearchScrollController = new SearchAndRecommendationsScrollController(
-                findViewById(R.id.search_and_recommendations_container));
-        mSearchScrollController.setCurrentRecyclerView(
-                findViewById(R.id.primary_widgets_list_view));
-        mSearchScrollController.mRecommendedWidgetsTable.setWidgetCellLongClickListener(this);
-        mSearchScrollController.mRecommendedWidgetsTable.setWidgetCellOnClickListener(this);
+
+        mSearchScrollView = findViewById(R.id.search_and_recommendations_container);
+        mSearchScrollView.setCurrentRecyclerView(findViewById(R.id.primary_widgets_list_view));
+
+        mRecommendedWidgetsTable = mSearchScrollView.findViewById(R.id.recommended_widget_table);
+        mRecommendedWidgetsTable.setWidgetCellLongClickListener(this);
+        mRecommendedWidgetsTable.setWidgetCellOnClickListener(this);
+
+        mTabBar = mSearchScrollView.findViewById(R.id.tabs);
+        mSearchBarContainer = mSearchScrollView.findViewById(R.id.search_bar_container);
+        mSearchBar = mSearchScrollView.findViewById(R.id.widgets_search_bar);
+        mHeaderTitle = mSearchScrollView.findViewById(R.id.title);
 
         onRecommendedWidgetsBound();
         onWidgetsBound();
 
-        mSearchScrollController.mSearchBar.initialize(
+        mSearchBar.initialize(
                 mActivityContext.getPopupDataProvider(), /* searchModeListener= */ this);
 
         setUpEducationViewsIfNeeded();
@@ -258,7 +271,7 @@
             reset();
             resetExpandedHeaders();
             mCurrentWidgetsRecyclerView = recyclerView;
-            mSearchScrollController.setCurrentRecyclerView(recyclerView);
+            mSearchScrollView.setCurrentRecyclerView(recyclerView);
         }
     }
 
@@ -285,7 +298,7 @@
             mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView.scrollToTop();
         }
         mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.scrollToTop();
-        mSearchScrollController.reset(/* animate= */ true);
+        mSearchScrollView.reset(/* animate= */ true);
     }
 
     @VisibleForTesting
@@ -355,8 +368,7 @@
 
     @Override
     protected void onContentHorizontalMarginChanged(int contentHorizontalMarginInPx) {
-        setContentViewChildHorizontalMargin(mSearchScrollController.mContainer,
-                contentHorizontalMarginInPx);
+        setContentViewChildHorizontalMargin(mSearchScrollView, contentHorizontalMarginInPx);
         if (mViewPager == null) {
             setContentViewChildHorizontalPadding(
                     mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView,
@@ -390,16 +402,8 @@
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         doMeasure(widthMeasureSpec, heightMeasureSpec);
 
-        if (mSearchScrollController.updateHeaderHeight()) {
-            doMeasure(widthMeasureSpec, heightMeasureSpec);
-        }
-
         if (updateMaxSpansPerRow()) {
             doMeasure(widthMeasureSpec, heightMeasureSpec);
-
-            if (mSearchScrollController.updateHeaderHeight()) {
-                doMeasure(widthMeasureSpec, heightMeasureSpec);
-            }
         }
     }
 
@@ -460,7 +464,7 @@
 
         if (mHasWorkProfile) {
             mViewPager.setVisibility(VISIBLE);
-            mSearchScrollController.mTabBar.setVisibility(VISIBLE);
+            mTabBar.setVisibility(VISIBLE);
             AdapterHolder workUserAdapterHolder = mAdapters.get(AdapterHolder.WORK);
             workUserAdapterHolder.mWidgetsListAdapter.setWidgets(allWidgets);
             onActivePageChanged(mViewPager.getCurrentPage());
@@ -508,10 +512,10 @@
     private void setViewVisibilityBasedOnSearch(boolean isInSearchMode) {
         mIsInSearchMode = isInSearchMode;
         if (isInSearchMode) {
-            mSearchScrollController.mRecommendedWidgetsTable.setVisibility(GONE);
+            mRecommendedWidgetsTable.setVisibility(GONE);
             if (mHasWorkProfile) {
                 mViewPager.setVisibility(GONE);
-                mSearchScrollController.mTabBar.setVisibility(GONE);
+                mTabBar.setVisibility(GONE);
             } else {
                 mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView.setVisibility(GONE);
             }
@@ -539,7 +543,6 @@
         }
         List<WidgetItem> recommendedWidgets =
                 mActivityContext.getPopupDataProvider().getRecommendedWidgets();
-        WidgetsRecommendationTableLayout table = mSearchScrollController.mRecommendedWidgetsTable;
         if (recommendedWidgets.size() > 0) {
             float noWidgetsViewHeight = 0;
             if (mIsNoWidgetsViewNeeded) {
@@ -562,9 +565,10 @@
             List<ArrayList<WidgetItem>> recommendedWidgetsInTable =
                     WidgetsTableUtils.groupWidgetItemsIntoTableWithoutReordering(
                             recommendedWidgets, mMaxSpansPerRow);
-            table.setRecommendedWidgets(recommendedWidgetsInTable, maxTableHeight);
+            mRecommendedWidgetsTable.setRecommendedWidgets(
+                    recommendedWidgetsInTable, maxTableHeight);
         } else {
-            table.setVisibility(GONE);
+            mRecommendedWidgetsTable.setVisibility(GONE);
         }
     }
 
@@ -577,7 +581,7 @@
             mOpenCloseAnimator.setValues(
                     PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
             mOpenCloseAnimator
-                    .setDuration(DEFAULT_OPEN_DURATION)
+                    .setDuration(mActivityContext.getDeviceProfile().bottomSheetOpenDuration)
                     .setInterpolator(AnimationUtils.loadInterpolator(
                             getContext(), android.R.interpolator.linear_out_slow_in));
             mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
@@ -598,7 +602,7 @@
 
     @Override
     protected void handleClose(boolean animate) {
-        handleClose(animate, DEFAULT_OPEN_DURATION);
+        handleClose(animate, mActivityContext.getDeviceProfile().bottomSheetCloseDuration);
     }
 
     @Override
@@ -619,10 +623,9 @@
                 mNoIntercept = !getRecyclerView().shouldContainerScroll(ev, getPopupContainer());
             }
 
-            if (mSearchScrollController.mSearchBar.isSearchBarFocused()
-                    && !getPopupContainer().isEventOverView(
-                    mSearchScrollController.mSearchBarContainer, ev)) {
-                mSearchScrollController.mSearchBar.clearSearchBarFocus();
+            if (mSearchBar.isSearchBarFocused()
+                    && !getPopupContainer().isEventOverView(mSearchBarContainer, ev)) {
+                mSearchBar.clearSearchBarFocus();
             }
         }
         return super.onControllerInterceptTouchEvent(ev);
@@ -663,8 +666,8 @@
 
     @Override
     public int getHeaderViewHeight() {
-        return measureHeightWithVerticalMargins(mSearchScrollController.mHeaderTitle)
-                + measureHeightWithVerticalMargins(mSearchScrollController.mSearchBarContainer);
+        return measureHeightWithVerticalMargins(mHeaderTitle)
+                + measureHeightWithVerticalMargins(mSearchBarContainer);
     }
 
     /** private the height, in pixel, + the vertical margins of a given view. */
@@ -681,14 +684,14 @@
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
         if (mIsInSearchMode) {
-            mSearchScrollController.mSearchBar.reset();
+            mSearchBar.reset();
         }
     }
 
     @Override
     public boolean onBackPressed() {
         if (mIsInSearchMode) {
-            mSearchScrollController.mSearchBar.reset();
+            mSearchBar.reset();
             return true;
         }
         return super.onBackPressed();
@@ -701,10 +704,9 @@
     }
 
     @Nullable private View getViewToShowEducationTip() {
-        if (mSearchScrollController.mRecommendedWidgetsTable.getVisibility() == VISIBLE
-                && mSearchScrollController.mRecommendedWidgetsTable.getChildCount() > 0) {
-            return ((ViewGroup) mSearchScrollController.mRecommendedWidgetsTable.getChildAt(0))
-                    .getChildAt(0);
+        if (mRecommendedWidgetsTable.getVisibility() == VISIBLE
+                && mRecommendedWidgetsTable.getChildCount() > 0) {
+            return ((ViewGroup) mRecommendedWidgetsTable.getChildAt(0)).getChildAt(0);
         }
 
         AdapterHolder adapterHolder = mAdapters.get(mIsInSearchMode
@@ -801,7 +803,7 @@
         }
 
         private int getEmptySpaceHeight() {
-            return mSearchScrollController.getHeaderHeight();
+            return mSearchScrollView.getHeaderHeight();
         }
 
         void setup(WidgetsRecyclerView recyclerView) {
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..5969e3e 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
@@ -20,23 +20,14 @@
 import android.graphics.Point;
 import android.util.AttributeSet;
 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;
+import com.android.launcher3.util.ScrollableLayoutManager;
 
 /**
  * The widgets recycler view.
@@ -51,13 +42,6 @@
     private boolean mTouchDownOnScroller;
     private HeaderViewDimensionsProvider mHeaderViewDimensionsProvider;
 
-    // Cached sizes
-    private int mLastVisibleWidgetContentTableHeight = 0;
-    private int mWidgetHeaderHeight = 0;
-    private int mWidgetEmptySpaceHeight = 0;
-
-    private final int mSpacingBetweenEntries;
-
     public WidgetsRecyclerView(Context context) {
         this(context, null);
     }
@@ -71,21 +55,12 @@
         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
     protected void onFinishInflate() {
         super.onFinishInflate();
-        // create a layout manager with Launcher's context so that scroll position
-        // can be preserved during screen rotation.
-        setLayoutManager(new LinearLayoutManager(getContext()));
+        setLayoutManager(new ScrollableLayoutManager(getContext()));
     }
 
     @Override
@@ -129,7 +104,7 @@
         }
 
         // Skip early if, there no child laid out in the container.
-        int scrollY = getCurrentScrollY();
+        int scrollY = computeVerticalScrollOffset();
         if (scrollY < 0) {
             mScrollbar.setThumbOffsetY(-1);
             return;
@@ -138,67 +113,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;
     }
@@ -240,39 +154,6 @@
     }
 
     /**
-     * Returns the sum of the height, in pixels, of this list adapter's items from index 0 until
-     * {@code untilIndex}.
-     *
-     * <p>If the untilIndex is larger than the total number of items in this adapter, returns the
-     * sum of all items' height.
-     */
-    private int getItemsHeight(int untilIndex) {
-        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);
-            }
-        }
-        return totalItemsHeight;
-    }
-
-    /**
      * Provides dimensions of the header view that is shown at the top of a
      * {@link WidgetsRecyclerView}.
      */
diff --git a/src/com/android/launcher3/widget/picker/WidgetsSpaceViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsSpaceViewHolderBinder.java
index 1aa5753..0c4f7aa 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsSpaceViewHolderBinder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsSpaceViewHolderBinder.java
@@ -15,16 +15,12 @@
  */
 package com.android.launcher3.widget.picker;
 
-import static android.view.View.MeasureSpec.EXACTLY;
-import static android.view.View.MeasureSpec.makeMeasureSpec;
-
-import android.content.Context;
-import android.view.View;
 import android.view.ViewGroup;
 
 import androidx.recyclerview.widget.RecyclerView.ViewHolder;
 
 import com.android.launcher3.recyclerview.ViewHolderBinder;
+import com.android.launcher3.views.StickyHeaderLayout.EmptySpaceView;
 import com.android.launcher3.widget.model.WidgetListSpaceEntry;
 
 import java.util.List;
@@ -52,64 +48,4 @@
             @ListPosition int position, List<Object> payloads) {
         ((EmptySpaceView) holder.itemView).setFixedHeight(mEmptySpaceHeightProvider.getAsInt());
     }
-
-    /**
-     * Empty view which allows listening for 'Y' changes
-     */
-    public static class EmptySpaceView extends View {
-
-        private Runnable mOnYChangeCallback;
-        private int mHeight = 0;
-
-        private EmptySpaceView(Context context) {
-            super(context);
-            animate().setUpdateListener(v -> notifyYChanged());
-        }
-
-        /**
-         * Sets the height for the empty view
-         * @return true if the height changed, false otherwise
-         */
-        public boolean setFixedHeight(int height) {
-            if (mHeight != height) {
-                mHeight = height;
-                requestLayout();
-                return true;
-            }
-            return false;
-        }
-
-        @Override
-        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-            super.onMeasure(widthMeasureSpec, makeMeasureSpec(mHeight, EXACTLY));
-        }
-
-        public void setOnYChangeCallback(Runnable callback) {
-            mOnYChangeCallback = callback;
-        }
-
-        @Override
-        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-            super.onLayout(changed, left, top, right, bottom);
-            notifyYChanged();
-        }
-
-        @Override
-        public void offsetTopAndBottom(int offset) {
-            super.offsetTopAndBottom(offset);
-            notifyYChanged();
-        }
-
-        @Override
-        public void setTranslationY(float translationY) {
-            super.setTranslationY(translationY);
-            notifyYChanged();
-        }
-
-        private void notifyYChanged() {
-            if (mOnYChangeCallback != null) {
-                mOnYChangeCallback.run();
-            }
-        }
-    }
 }
diff --git a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
index 702f343..13ad7a4 100644
--- a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
+++ b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
@@ -62,6 +62,8 @@
 
     // True is the widget support is disabled.
     public static final boolean GO_DISABLE_WIDGETS = false;
+    // True is the shortcut support is disabled.
+    public static final boolean GO_DISABLE_SHORTCUTS = false;
     public static final boolean GO_DISABLE_NOTIFICATION_DOTS = false;
 
     private static final String TAG = "WidgetsModel";
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
index 6715749..c8b5e2f 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -17,7 +17,6 @@
 package com.android.launcher3.uioverrides;
 
 import android.app.Person;
-import android.content.Context;
 import android.content.pm.ShortcutInfo;
 
 import com.android.launcher3.Utilities;
@@ -29,11 +28,4 @@
     public static Person[] getPersons(ShortcutInfo si) {
         return Utilities.EMPTY_PERSON_ARRAY;
     }
-
-    /**
-     * Returns the minimum space that should be left empty at the end of hotseat
-     */
-    public static int getHotseatEndOffset(Context context) {
-        return 0;
-    }
 }
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java b/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java
index bf35dd8..a581f91 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -32,7 +32,6 @@
 public class AllAppsState extends LauncherState {
 
     private static final float PARALLAX_COEFFICIENT = .125f;
-    private static final float WORKSPACE_SCALE_FACTOR = 0.97f;
 
     private static final int STATE_FLAGS = FLAG_WORKSPACE_INACCESSIBLE;
 
@@ -43,9 +42,9 @@
     @Override
     public <DEVICE_PROFILE_CONTEXT extends Context & DeviceProfileListenable>
     int getTransitionDuration(DEVICE_PROFILE_CONTEXT context, boolean isToState) {
-        return !context.getDeviceProfile().isTablet && isToState
-                ? 600
-                : isToState ? 500 : 300;
+        return isToState
+                ? context.getDeviceProfile().allAppsOpenDuration
+                : context.getDeviceProfile().allAppsCloseDuration;
     }
 
     @Override
@@ -60,7 +59,8 @@
 
     @Override
     public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
-        return new ScaleAndTranslation(WORKSPACE_SCALE_FACTOR, NO_OFFSET, NO_OFFSET);
+        return new ScaleAndTranslation(launcher.getDeviceProfile().workspaceContentScale, NO_OFFSET,
+                NO_OFFSET);
     }
 
     @Override
@@ -71,7 +71,7 @@
             ScaleAndTranslation overviewScaleAndTranslation = LauncherState.OVERVIEW
                     .getWorkspaceScaleAndTranslation(launcher);
             return new ScaleAndTranslation(
-                    WORKSPACE_SCALE_FACTOR,
+                    launcher.getDeviceProfile().workspaceContentScale,
                     overviewScaleAndTranslation.translationX,
                     overviewScaleAndTranslation.translationY);
         }
diff --git a/tests/Android.bp b/tests/Android.bp
index 54cded0..1584308 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -24,6 +24,18 @@
       "src/**/*.java",
       "src/**/*.kt"
     ],
+    exclude_srcs: [
+        ":launcher-non-quickstep-tests-src"
+    ],
+}
+
+// Source code used for non-quickstep tests
+filegroup {
+    name: "launcher-non-quickstep-tests-src",
+    srcs: [
+       "src/com/android/launcher3/nonquickstep/**/*.java",
+       "src/com/android/launcher3/nonquickstep/**/*.kt",
+    ],
 }
 
 // Source code used for oop test helpers
@@ -84,6 +96,7 @@
     name: "Launcher3Tests",
     srcs: [
         ":launcher-tests-src",
+        ":launcher-non-quickstep-tests-src",
     ],
     static_libs: ["Launcher3TestLib"],
     libs: [
diff --git a/tests/src/com/android/launcher3/DeviceProfileBaseTest.kt b/tests/src/com/android/launcher3/DeviceProfileBaseTest.kt
index 6d0fcb6..2c1cbdf 100644
--- a/tests/src/com/android/launcher3/DeviceProfileBaseTest.kt
+++ b/tests/src/com/android/launcher3/DeviceProfileBaseTest.kt
@@ -17,12 +17,18 @@
 
 import android.content.Context
 import android.graphics.PointF
+import android.graphics.Rect
+import android.util.SparseArray
 import androidx.test.core.app.ApplicationProvider
+import com.android.launcher3.DeviceProfile.DEFAULT_PROVIDER;
 import com.android.launcher3.util.DisplayController.Info
 import com.android.launcher3.util.WindowBounds
 import org.junit.Before
 import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mockito.mock
+import java.io.PrintWriter
+import java.io.StringWriter
 import org.mockito.Mockito.`when` as whenever
 
 abstract class DeviceProfileBaseTest {
@@ -35,6 +41,9 @@
     protected var transposeLayoutWithOrientation: Boolean = false
     protected var useTwoPanels: Boolean = false
     protected var isGestureMode: Boolean = true
+    protected var devicePaddingsMock: DevicePaddings = mock(DevicePaddings::class.java)
+    protected var staticdevicePaddingsMock: DevicePaddings.DevicePadding =
+            mock(DevicePaddings.DevicePadding::class.java)
 
     @Before
     fun setUp() {
@@ -49,88 +58,305 @@
         inv,
         info,
         windowBounds,
+        SparseArray(),
         isMultiWindowMode,
         transposeLayoutWithOrientation,
         useTwoPanels,
-        isGestureMode
+        isGestureMode,
+        DEFAULT_PROVIDER
     )
 
-    protected fun initializeVarsForPhone(isLandscape: Boolean = false) {
-        val (x, y) = if (isLandscape)
-            Pair(3120, 1440)
+    protected fun initializeVarsForPhone(isGestureMode: Boolean = true,
+                                         isVerticalBar: Boolean = false) {
+        val (x, y) = if (isVerticalBar)
+            Pair(2400, 1080)
         else
-            Pair(1440, 3120)
+            Pair(1080, 2400)
 
-        windowBounds = WindowBounds(x, y, x, y - 100, 0)
+        windowBounds = WindowBounds(Rect(0, 0, x, y), Rect(
+                if (isVerticalBar) 118 else 0,
+                if (isVerticalBar) 74 else 118,
+                if (!isGestureMode && isVerticalBar) 126 else 0,
+                if (isGestureMode) 63 else if (isVerticalBar) 0 else 126))
 
         whenever(info.isTablet(any())).thenReturn(false)
-        whenever(info.getDensityDpi()).thenReturn(560)
+        whenever(info.getDensityDpi()).thenReturn(420)
+        whenever(info.smallestSizeDp(any())).thenReturn(411f)
+        whenever(devicePaddingsMock.getDevicePadding(anyInt())).thenReturn(staticdevicePaddingsMock)
+        whenever(staticdevicePaddingsMock.getWorkspaceTopPadding(anyInt())).thenReturn(95)
+        whenever(staticdevicePaddingsMock.getWorkspaceBottomPadding(anyInt())).thenReturn(116)
+        whenever(staticdevicePaddingsMock.maxEmptySpacePx)
+                .thenReturn(if (isVerticalBar) if (isGestureMode) 131 else 184 else 315)
 
-        inv = newScalableInvariantDeviceProfile()
+        this.isGestureMode = isGestureMode
+        transposeLayoutWithOrientation = true
+
+        inv = InvariantDeviceProfile().apply {
+            numRows = 5
+            numColumns = 4
+            numSearchContainerColumns = 4
+
+            iconSize = floatArrayOf(60f, 54f, 60f, 60f)
+            iconTextSize = FloatArray(4) { 14f }
+            deviceType = InvariantDeviceProfile.TYPE_PHONE
+
+            minCellSize = listOf(
+                    PointF(80f, 104f),
+                    PointF(80f, 104f),
+                    PointF(80f, 104f),
+                    PointF(80f, 104f)
+            ).toTypedArray()
+
+            borderSpaces = listOf(
+                    PointF(16f, 16f),
+                    PointF(16f, 16f),
+                    PointF(16f, 16f),
+                    PointF(16f, 16f)
+            ).toTypedArray()
+
+            numFolderRows = 3
+            numFolderColumns = 3
+            folderBorderSpaces = PointF(16f, 16f)
+            folderTopPadding = 24f
+            folderCellSize = PointF(80f, 94f)
+
+
+            inlineNavButtonsEndSpacing = R.dimen.taskbar_button_margin_4_5
+
+            horizontalMargin = FloatArray(4) { 22f }
+
+            allAppsCellSize = listOf(
+                    PointF(80f, 104f),
+                    PointF(80f, 104f),
+                    PointF(80f, 104f),
+                    PointF(80f, 104f)
+            ).toTypedArray()
+            allAppsIconSize = floatArrayOf(60f, 60f, 60f, 60f)
+            allAppsIconTextSize = FloatArray(4) { 14f }
+            allAppsBorderSpaces = listOf(
+                    PointF(16f, 16f),
+                    PointF(16f, 16f),
+                    PointF(16f, 16f),
+                    PointF(16f, 16f)
+            ).toTypedArray()
+
+            numShownHotseatIcons = 4
+
+            numDatabaseHotseatIcons = 4
+
+            hotseatColumnSpan = IntArray(4) { 4 }
+            hotseatBarBottomSpace = FloatArray(4) { 48f }
+            hotseatQsbSpace = FloatArray(4) { 36f }
+
+            numAllAppsColumns = 4
+
+            isScalable = true
+
+            inlineQsb = BooleanArray(4) { false }
+
+            devicePaddings = devicePaddingsMock
+        }
     }
 
-    protected fun initializeVarsForTablet(isLandscape: Boolean = false) {
+    protected fun initializeVarsForTablet(isLandscape: Boolean = false,
+                                          isGestureMode: Boolean = true) {
         val (x, y) = if (isLandscape)
             Pair(2560, 1600)
         else
             Pair(1600, 2560)
 
-        windowBounds = WindowBounds(x, y, x, y - 100, 0)
+        windowBounds =
+                WindowBounds(Rect(0, 0, x, y), Rect(0, 104, 0, 0))
 
         whenever(info.isTablet(any())).thenReturn(true)
         whenever(info.getDensityDpi()).thenReturn(320)
+        whenever(info.smallestSizeDp(any())).thenReturn(800f)
+        whenever(devicePaddingsMock.getDevicePadding(anyInt())).thenReturn(staticdevicePaddingsMock)
+        whenever(staticdevicePaddingsMock.getWorkspaceTopPadding(anyInt()))
+                .thenReturn(if (isLandscape) 32 else 159)
+        whenever(staticdevicePaddingsMock.getWorkspaceBottomPadding(anyInt()))
+                .thenReturn(if (isLandscape) 72 else 203)
+        whenever(staticdevicePaddingsMock.maxEmptySpacePx).thenReturn(if (isLandscape) 200 else 19998)
 
-        inv = newScalableInvariantDeviceProfile()
+
+        this.isGestureMode = isGestureMode
+        useTwoPanels = false
+
+        inv = InvariantDeviceProfile().apply {
+            numRows = 5
+            numColumns = 6
+            numSearchContainerColumns = 3
+
+            iconSize = FloatArray(4) { 60f }
+            iconTextSize = FloatArray(4) { 14f }
+            deviceType = InvariantDeviceProfile.TYPE_TABLET
+
+            minCellSize = listOf(
+                    PointF(102f, 120f),
+                    PointF(120f, 104f),
+                    PointF(102f, 120f),
+                    PointF(102f, 120f)
+            ).toTypedArray()
+
+            borderSpaces = listOf(
+                    PointF(16f, 64f),
+                    PointF(64f, 16f),
+                    PointF(16f, 64f),
+                    PointF(16f, 64f)
+            ).toTypedArray()
+
+            numFolderRows = 3
+            numFolderColumns = 3
+            folderBorderSpaces = PointF(16f, 16f)
+            folderTopPadding = 24f
+            folderCellSize = PointF(120f, 104f)
+
+            inlineNavButtonsEndSpacing = R.dimen.taskbar_button_margin_6_5
+
+            horizontalMargin = floatArrayOf(54f, 120f, 54f, 54f)
+
+            allAppsCellSize = listOf(
+                    PointF(96f, 142f),
+                    PointF(126f, 126f),
+                    PointF(96f, 142f),
+                    PointF(96f, 142f)
+            ).toTypedArray()
+            allAppsIconSize = FloatArray(4) { 60f }
+            allAppsIconTextSize = FloatArray(4) { 14f }
+            allAppsBorderSpaces = listOf(
+                    PointF(8f, 16f),
+                    PointF(16f, 16f),
+                    PointF(8f, 16f),
+                    PointF(8f, 16f)
+            ).toTypedArray()
+
+            numShownHotseatIcons = 6
+
+            numDatabaseHotseatIcons = 6
+
+            hotseatColumnSpan = intArrayOf(6, 4, 6, 6)
+            hotseatBarBottomSpace = floatArrayOf(36f, 40f, 36f, 36f)
+            hotseatQsbSpace = FloatArray(4) { 32f }
+
+            numAllAppsColumns = 6
+
+            isScalable = true
+            devicePaddingId = 2132148242 // "@xml/paddings_6x5"
+
+            inlineQsb = booleanArrayOf(
+                    false,
+                    true,
+                    false,
+                    false
+            )
+
+            devicePaddings = devicePaddingsMock
+        }
     }
 
-    /**
-     * A very generic grid, just to make qsb tests work. For real calculations, make sure to use
-     * values that better represent a real grid.
-     */
-    protected fun newScalableInvariantDeviceProfile(): InvariantDeviceProfile =
-        InvariantDeviceProfile().apply {
-            isScalable = true
-            numColumns = 4
+    protected fun initializeVarsForTwoPanel(isLandscape: Boolean = false,
+            isGestureMode: Boolean = true) {
+        val (x, y) = if (isLandscape)
+            Pair(2208, 1840)
+        else
+            Pair(1840, 2208)
+
+        windowBounds = WindowBounds(Rect(0, 0, x, y),
+                Rect(0, 110, 0, 0))
+
+        whenever(info.isTablet(any())).thenReturn(true)
+        whenever(info.getDensityDpi()).thenReturn(420)
+        whenever(info.smallestSizeDp(any())).thenReturn(700f)
+        whenever(devicePaddingsMock.getDevicePadding(anyInt())).thenReturn(staticdevicePaddingsMock)
+
+        val topPadding = if (isLandscape) 18 else 89
+        val bottomPadding = if (isLandscape) 39 else 146
+        val maxEmptySpace = if (isLandscape) 131 else 236
+        whenever(staticdevicePaddingsMock.getWorkspaceTopPadding(anyInt())).thenReturn(topPadding)
+        whenever(staticdevicePaddingsMock.getWorkspaceBottomPadding(anyInt()))
+                .thenReturn(bottomPadding)
+        whenever(staticdevicePaddingsMock.maxEmptySpacePx).thenReturn(maxEmptySpace)
+
+        this.isGestureMode = isGestureMode
+        useTwoPanels = true
+
+        inv = InvariantDeviceProfile().apply {
             numRows = 4
-            numShownHotseatIcons = 4
-            numDatabaseHotseatIcons = 6
-            numShrunkenHotseatIcons = 5
-            horizontalMargin = FloatArray(4) { 22f }
-            borderSpaces = listOf(
-                PointF(16f, 16f),
-                PointF(16f, 16f),
-                PointF(16f, 16f),
-                PointF(16f, 16f)
-            ).toTypedArray()
-            allAppsBorderSpaces = listOf(
-                PointF(16f, 16f),
-                PointF(16f, 16f),
-                PointF(16f, 16f),
-                PointF(16f, 16f)
-            ).toTypedArray()
-            hotseatBorderSpaces = FloatArray(4) { 16f }
-            hotseatColumnSpan = IntArray(4) { 4 }
-            iconSize = FloatArray(4) { 56f }
-            allAppsIconSize = FloatArray(4) { 56f }
-            iconTextSize = FloatArray(4) { 14f }
-            allAppsIconTextSize = FloatArray(4) { 14f }
+            numColumns = 4
+            numSearchContainerColumns = 4
+
+            iconSize = floatArrayOf(60f, 52f, 52f, 60f)
+            iconTextSize = floatArrayOf(14f, 14f, 12f, 14f)
+            deviceType = InvariantDeviceProfile.TYPE_MULTI_DISPLAY
+
             minCellSize = listOf(
-                PointF(64f, 83f),
-                PointF(64f, 83f),
-                PointF(64f, 83f),
-                PointF(64f, 83f)
+                    PointF(80f, 104f),
+                    PointF(80f, 104f),
+                    PointF(68f, 116f),
+                    PointF(80f, 102f)
             ).toTypedArray()
+
+            borderSpaces = listOf(
+                    PointF(16f, 16f),
+                    PointF(16f, 16f),
+                    PointF(16f, 20f),
+                    PointF(20f, 20f)
+            ).toTypedArray()
+
+            numFolderRows = 3
+            numFolderColumns = 3
+            folderBorderSpaces = PointF(16f, 16f)
+            folderTopPadding = 24f
+            folderCellSize = PointF(80f, 94f)
+
+            inlineNavButtonsEndSpacing = R.dimen.taskbar_button_margin_4_4
+
+            horizontalMargin = floatArrayOf(21.5f, 21.5f, 22.5f, 30.5f)
+
             allAppsCellSize = listOf(
-                PointF(64f, 83f),
-                PointF(64f, 83f),
-                PointF(64f, 83f),
-                PointF(64f, 83f)
+                    PointF(0f, 0f),
+                    PointF(0f, 0f),
+                    PointF(68f, 104f),
+                    PointF(80f, 104f)
             ).toTypedArray()
+            allAppsIconSize = floatArrayOf(60f, 60f, 52f, 60f)
+            allAppsIconTextSize = floatArrayOf(14f, 14f, 12f, 14f)
+            allAppsBorderSpaces = listOf(
+                    PointF(16f, 16f),
+                    PointF(16f, 16f),
+                    PointF(16f, 28f),
+                    PointF(20f, 16f)
+            ).toTypedArray()
+
+            numShownHotseatIcons = 6
+
+            numDatabaseHotseatIcons = 6
+
+            hotseatColumnSpan = IntArray(4) { 6 }
+            hotseatBarBottomSpace = floatArrayOf(48f, 48f, 36f, 20f)
+            hotseatQsbSpace = floatArrayOf(36f, 36f, 36f, 28f)
+
+            numAllAppsColumns = 6
+            numDatabaseAllAppsColumns = 6
+
+            isScalable = true
+
             inlineQsb = booleanArrayOf(
-                false,
-                false,
-                false,
-                false
+                    false,
+                    false,
+                    false,
+                    false
             )
+
+            devicePaddings = devicePaddingsMock
         }
+    }
+
+    fun dump(dp: DeviceProfile): String {
+        val stringWriter = StringWriter()
+        val printWriter = PrintWriter(stringWriter)
+        dp.dump(context, "", printWriter)
+        printWriter.flush()
+        return stringWriter.toString()
+    }
 }
\ No newline at end of file
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/HotseatShownIconsTest.kt b/tests/src/com/android/launcher3/HotseatShownIconsTest.kt
deleted file mode 100644
index 593239d..0000000
--- a/tests/src/com/android/launcher3/HotseatShownIconsTest.kt
+++ /dev/null
@@ -1,212 +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 androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.launcher3.InvariantDeviceProfile.TYPE_MULTI_DISPLAY
-import com.android.launcher3.InvariantDeviceProfile.TYPE_PHONE
-import com.android.launcher3.InvariantDeviceProfile.TYPE_TABLET
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-
-/**
- * Test for [DeviceProfile]
- */
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class HotseatShownIconsTest : DeviceProfileBaseTest() {
-
-    @Test
-    fun hotseat_size_is_normal_for_handhelds() {
-        initializeVarsForPhone()
-        inv = newScalableInvariantDeviceProfile().apply {
-            deviceType = TYPE_PHONE
-        }
-
-        val dp = newDP()
-
-        assertThat(dp.isQsbInline).isFalse()
-        assertThat(dp.numShownHotseatIcons).isEqualTo(4)
-    }
-
-    @Test
-    fun hotseat_size_is_max_when_large_screen() {
-        initializeVarsForTablet(isLandscape = true)
-        inv = newScalableInvariantDeviceProfile().apply {
-            deviceType = TYPE_MULTI_DISPLAY
-        }
-        useTwoPanels = true
-
-        val dp = newDP()
-
-        assertThat(dp.isQsbInline).isFalse()
-        assertThat(dp.numShownHotseatIcons).isEqualTo(6)
-    }
-
-    @Test
-    fun hotseat_size_is_shrunk_if_needed_when_large_screen() {
-        initializeVarsForTablet(isLandscape = true)
-        inv = newScalableInvariantDeviceProfile().apply {
-            deviceType = TYPE_MULTI_DISPLAY
-            inlineQsb = booleanArrayOf(
-                false,
-                false,
-                false,
-                true // two panels landscape
-            )
-        }
-        useTwoPanels = true
-
-        isGestureMode = false
-        val dp = newDP()
-
-        if (dp.hotseatQsbHeight > 0) {
-            assertThat(dp.isQsbInline).isTrue()
-            assertThat(dp.numShownHotseatIcons).isEqualTo(5)
-        } else { // Launcher3 doesn't have QSB height
-            assertThat(dp.isQsbInline).isFalse()
-            assertThat(dp.numShownHotseatIcons).isEqualTo(6)
-        }
-    }
-
-    /**
-     * For consistency, the hotseat should shrink if any orientation on the device type has an
-     * inline qsb
-     */
-    @Test
-    fun hotseat_size_is_shrunk_even_in_portrait_when_large_screen() {
-        initializeVarsForTablet()
-        inv = newScalableInvariantDeviceProfile().apply {
-            deviceType = TYPE_MULTI_DISPLAY
-            inlineQsb = booleanArrayOf(
-                false,
-                false,
-                false,
-                true // two panels landscape
-            )
-        }
-        useTwoPanels = true
-
-        isGestureMode = false
-        val dp = newDP()
-
-        if (dp.hotseatQsbHeight > 0) {
-            assertThat(dp.isQsbInline).isFalse()
-            assertThat(dp.numShownHotseatIcons).isEqualTo(5)
-        } else { // Launcher3 doesn't have QSB height
-            assertThat(dp.isQsbInline).isFalse()
-            assertThat(dp.numShownHotseatIcons).isEqualTo(6)
-        }
-    }
-
-    @Test
-    fun hotseat_size_is_default_when_small_screen() {
-        initializeVarsForPhone()
-        inv = newScalableInvariantDeviceProfile().apply {
-            deviceType = TYPE_MULTI_DISPLAY
-        }
-        useTwoPanels = true
-
-        val dp = newDP()
-
-        assertThat(dp.numShownHotseatIcons).isEqualTo(4)
-    }
-
-    @Test
-    fun hotseat_size_is_not_shrunk_on_gesture_tablet() {
-        initializeVarsForTablet(isLandscape = true)
-        inv = newScalableInvariantDeviceProfile().apply {
-            deviceType = TYPE_TABLET
-            inlineQsb = booleanArrayOf(
-                    false,
-                    true, // landscape
-                    false,
-                    false
-            )
-            numShownHotseatIcons = 6
-        }
-
-        isGestureMode = true
-        val dp = newDP()
-
-        if (dp.hotseatQsbHeight > 0) {
-            assertThat(dp.isQsbInline).isTrue()
-            assertThat(dp.numShownHotseatIcons).isEqualTo(6)
-        } else { // Launcher3 doesn't have QSB height
-            assertThat(dp.isQsbInline).isFalse()
-            assertThat(dp.numShownHotseatIcons).isEqualTo(6)
-        }
-    }
-
-    @Test
-    fun hotseat_size_is_shrunk_if_needed_on_tablet() {
-        initializeVarsForTablet(isLandscape = true)
-        inv = newScalableInvariantDeviceProfile().apply {
-            deviceType = TYPE_TABLET
-            inlineQsb = booleanArrayOf(
-                false,
-                true, // landscape
-                false,
-                false
-            )
-            numShownHotseatIcons = 6
-        }
-
-        isGestureMode = false
-        val dp = newDP()
-
-        if (dp.hotseatQsbHeight > 0) {
-            assertThat(dp.isQsbInline).isTrue()
-            assertThat(dp.numShownHotseatIcons).isEqualTo(5)
-        } else { // Launcher3 doesn't have QSB height
-            assertThat(dp.isQsbInline).isFalse()
-            assertThat(dp.numShownHotseatIcons).isEqualTo(6)
-        }
-    }
-
-    /**
-     * For consistency, the hotseat should shrink if any orientation on the device type has an
-     * inline qsb
-     */
-    @Test
-    fun hotseat_size_is_shrunk_even_in_portrait_on_tablet() {
-        initializeVarsForTablet()
-        inv = newScalableInvariantDeviceProfile().apply {
-            deviceType = TYPE_TABLET
-            inlineQsb = booleanArrayOf(
-                false,
-                true, // landscape
-                false,
-                false
-            )
-            numShownHotseatIcons = 6
-        }
-
-        isGestureMode = false
-        val dp = newDP()
-
-        if (dp.hotseatQsbHeight > 0) {
-            assertThat(dp.isQsbInline).isFalse()
-            assertThat(dp.numShownHotseatIcons).isEqualTo(5)
-        } else { // Launcher3 doesn't have QSB height
-            assertThat(dp.isQsbInline).isFalse()
-            assertThat(dp.numShownHotseatIcons).isEqualTo(6)
-        }
-    }
-
-}
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/InlineQsbTest.kt b/tests/src/com/android/launcher3/InlineQsbTest.kt
deleted file mode 100644
index 905c1e1..0000000
--- a/tests/src/com/android/launcher3/InlineQsbTest.kt
+++ /dev/null
@@ -1,103 +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 androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-
-/**
- * Test for [DeviceProfile]
- */
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class InlineQsbTest : DeviceProfileBaseTest() {
-
-    @Test
-    fun qsb_is_not_inline_for_phones() {
-        initializeVarsForPhone()
-
-        val dp = newDP()
-
-        assertThat(dp.isQsbInline).isFalse()
-    }
-
-    @Test
-    fun qsb_is_inline_for_tablet_portrait() {
-        initializeVarsForTablet()
-        inv = newScalableInvariantDeviceProfile().apply {
-            inlineQsb = booleanArrayOf(
-                false,
-                true, // landscape
-                false,
-                false
-            )
-        }
-
-        val dp = DeviceProfile(
-            context,
-            inv,
-            info,
-            windowBounds,
-            isMultiWindowMode,
-            transposeLayoutWithOrientation,
-            useTwoPanels,
-            isGestureMode
-        )
-
-        assertThat(dp.isQsbInline).isFalse()
-    }
-
-    @Test
-    fun qsb_is_inline_for_tablet_landscape() {
-        initializeVarsForTablet(isLandscape = true)
-        inv = newScalableInvariantDeviceProfile().apply {
-            inlineQsb = booleanArrayOf(
-                false,
-                true, // landscape
-                false,
-                false
-            )
-            numColumns = 6
-            numRows = 5
-            numShownHotseatIcons = 6
-        }
-
-        val dp = newDP()
-
-        if (dp.hotseatQsbHeight > 0) {
-            assertThat(dp.isQsbInline).isTrue()
-        } else { // Launcher3 doesn't have QSB height
-            assertThat(dp.isQsbInline).isFalse()
-        }
-    }
-
-    /**
-     * This test is to make sure that a tablet doesn't inline the QSB if the layout doesn't support
-     */
-    @Test
-    fun qsb_is_not_inline_for_tablet_landscape_without_inline() {
-        initializeVarsForTablet(isLandscape = true)
-        useTwoPanels = true
-
-        val dp = newDP()
-
-        assertThat(dp.isQsbInline).isFalse()
-    }
-
-}
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/celllayout/CellLayoutBoard.java b/tests/src/com/android/launcher3/celllayout/CellLayoutBoard.java
new file mode 100644
index 0000000..a32ce3c
--- /dev/null
+++ b/tests/src/com/android/launcher3/celllayout/CellLayoutBoard.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.celllayout;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+
+
+public class CellLayoutBoard {
+
+    public static class CellType {
+        // The cells marked by this will be filled by 1x1 widgets and will be ignored when
+        // validating
+        public static final char IGNORE = 'x';
+        // The cells marked by this will be filled by app icons
+        public static final char ICON = 'i';
+        // Empty space
+        public static final char EMPTY = '-';
+        // Widget that will be saved as "main widget" for easier retrieval
+        public static final char MAIN_WIDGET = 'm';
+        // Everything else will be consider a widget
+    }
+
+    public static class WidgetRect {
+        public char mType;
+        public Rect mBounds;
+
+        WidgetRect(char type, Rect bounds) {
+            this.mType = type;
+            this.mBounds = bounds;
+        }
+
+        int getSpanX() {
+            return mBounds.right - mBounds.left + 1;
+        }
+
+        int getSpanY() {
+            return mBounds.top - mBounds.bottom + 1;
+        }
+
+        int getCellX() {
+            return mBounds.left;
+        }
+
+        int getCellY() {
+            return mBounds.bottom;
+        }
+
+        boolean shouldIgnore() {
+            return this.mType == CellType.IGNORE;
+        }
+
+        @Override
+        public String toString() {
+            return "WidgetRect type = " + mType + " bounds = " + mBounds.toString();
+        }
+    }
+
+    public static class IconPoint {
+        public Point coord;
+        public char mType;
+
+        public IconPoint(Point coord, char type) {
+            this.coord = coord;
+            mType = type;
+        }
+
+        public char getType() {
+            return mType;
+        }
+
+        public void setType(char type) {
+            mType = type;
+        }
+
+        public Point getCoord() {
+            return coord;
+        }
+
+        public void setCoord(Point coord) {
+            this.coord = coord;
+        }
+    }
+
+    static final int INFINITE = 99999;
+
+    char[][] mWidget = new char[30][30];
+
+    List<WidgetRect> mWidgetsRects = new ArrayList<>();
+    Map<Character, WidgetRect> mWidgetsMap = new HashMap<>();
+
+    List<IconPoint> mIconPoints = new ArrayList<>();
+    Map<Character, IconPoint> mIconsMap = new HashMap<>();
+
+    Point mMain = new Point();
+
+    CellLayoutBoard() {
+        for (int x = 0; x < mWidget.length; x++) {
+            for (int y = 0; y < mWidget[0].length; y++) {
+                mWidget[x][y] = CellType.EMPTY;
+            }
+        }
+    }
+
+    public List<WidgetRect> getWidgets() {
+        return mWidgetsRects;
+    }
+
+    public List<IconPoint> getIcons() {
+        return mIconPoints;
+    }
+
+    public Point getMain() {
+        return mMain;
+    }
+
+    public WidgetRect getWidgetRect(char c) {
+        return mWidgetsMap.get(c);
+    }
+
+    public static WidgetRect getWidgetRect(int x, int y, Set<Point> used, char[][] board) {
+        char type = board[x][y];
+        Queue<Point> search = new ArrayDeque<Point>();
+        Point current = new Point(x, y);
+        search.add(current);
+        used.add(current);
+        List<Point> neighbors = new ArrayList<>(List.of(
+                new Point(-1, 0),
+                new Point(0, -1),
+                new Point(1, 0),
+                new Point(0, 1))
+        );
+        Rect widgetRect = new Rect(INFINITE, -INFINITE, -INFINITE, INFINITE);
+        while (!search.isEmpty()) {
+            current = search.poll();
+            widgetRect.top = Math.max(widgetRect.top, current.y);
+            widgetRect.right = Math.max(widgetRect.right, current.x);
+            widgetRect.bottom = Math.min(widgetRect.bottom, current.y);
+            widgetRect.left = Math.min(widgetRect.left, current.x);
+            for (Point p : neighbors) {
+                Point next = new Point(current.x + p.x, current.y + p.y);
+                if (next.x < 0 || next.x >= board.length) continue;
+                if (next.y < 0 || next.y >= board[0].length) continue;
+                if (board[next.x][next.y] == type && !used.contains(next)) {
+                    used.add(next);
+                    search.add(next);
+                }
+            }
+        }
+        return new WidgetRect(type, widgetRect);
+    }
+
+    public static boolean isWidget(char type) {
+        return type != CellType.ICON && type != CellType.EMPTY;
+    }
+
+    public static boolean isIcon(char type) {
+        return type == CellType.ICON;
+    }
+
+    private static List<WidgetRect> getRects(char[][] board) {
+        Set<Point> used = new HashSet<>();
+        List<WidgetRect> widgetsRects = new ArrayList<>();
+        for (int x = 0; x < board.length; x++) {
+            for (int y = 0; y < board[0].length; y++) {
+                if (!used.contains(new Point(x, y)) && isWidget(board[x][y])) {
+                    widgetsRects.add(getWidgetRect(x, y, used, board));
+                }
+            }
+        }
+        return widgetsRects;
+    }
+
+    private static List<IconPoint> getIconPoints(char[][] board) {
+        List<IconPoint> iconPoints = new ArrayList<>();
+        for (int x = 0; x < board.length; x++) {
+            for (int y = 0; y < board[0].length; y++) {
+                if (isIcon(board[x][y])) {
+                    iconPoints.add(new IconPoint(new Point(x, y), board[x][y]));
+                }
+            }
+        }
+        return iconPoints;
+    }
+
+    public static CellLayoutBoard boardFromString(String boardStr) {
+        String[] lines = boardStr.split("\n");
+        CellLayoutBoard board = new CellLayoutBoard();
+
+        for (int y = 0; y < lines.length; y++) {
+            String line = lines[y];
+            for (int x = 0; x < line.length(); x++) {
+                char c = line.charAt(x);
+                if (c == CellType.MAIN_WIDGET) {
+                    board.mMain = new Point(x, y);
+                }
+                if (c != CellType.EMPTY) {
+                    board.mWidget[x][y] = line.charAt(x);
+                }
+            }
+        }
+        board.mWidgetsRects = getRects(board.mWidget);
+        board.mWidgetsRects.forEach(
+                widgetRect -> board.mWidgetsMap.put(widgetRect.mType, widgetRect));
+        board.mIconPoints = getIconPoints(board.mWidget);
+        return board;
+    }
+}
diff --git a/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java b/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
new file mode 100644
index 0000000..8ce932d
--- /dev/null
+++ b/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.celllayout;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.ui.AbstractLauncherUiTest;
+import com.android.launcher3.util.ContentWriter;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+
+public class FavoriteItemsTransaction {
+    private ArrayList<ItemInfo> mItemsToSubmit;
+    private Context mContext;
+    private ContentResolver mResolver;
+    public AbstractLauncherUiTest mTest;
+
+    public FavoriteItemsTransaction(Context context, AbstractLauncherUiTest test) {
+        mItemsToSubmit = new ArrayList<>();
+        mContext = context;
+        mResolver = mContext.getContentResolver();
+        mTest = test;
+    }
+
+    public FavoriteItemsTransaction addItem(ItemInfo itemInfo) {
+        this.mItemsToSubmit.add(itemInfo);
+        return this;
+    }
+
+    public FavoriteItemsTransaction removeLast() {
+        this.mItemsToSubmit.remove(this.mItemsToSubmit.size() - 1);
+        return this;
+    }
+
+    /**
+     * Commits all the ItemInfo into the database of Favorites
+     **/
+    public void commit() throws ExecutionException, InterruptedException {
+        List<ContentValues> values = new ArrayList<>();
+        for (ItemInfo item : this.mItemsToSubmit) {
+            ContentWriter writer = new ContentWriter(mContext);
+            item.onAddToDatabase(writer);
+            writer.put(LauncherSettings.Favorites._ID, item.id);
+            values.add(writer.getValues(mContext));
+        }
+        // Submit the icons to the database in the model thread to prevent race conditions
+        MODEL_EXECUTOR.submit(() -> mResolver.bulkInsert(LauncherSettings.Favorites.CONTENT_URI,
+                values.toArray(new ContentValues[0]))).get();
+        // Reload the state of the Launcher
+        MAIN_EXECUTOR.submit(() -> LauncherAppState.getInstance(
+                mContext).getModel().forceReload()).get();
+    }
+}
diff --git a/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java b/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java
new file mode 100644
index 0000000..2846cae
--- /dev/null
+++ b/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.celllayout;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.Point;
+import android.util.Log;
+import android.view.View;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.celllayout.testcases.FullReorderCase;
+import com.android.launcher3.celllayout.testcases.MoveOutReorderCase;
+import com.android.launcher3.celllayout.testcases.PushReorderCase;
+import com.android.launcher3.celllayout.testcases.ReorderTestCase;
+import com.android.launcher3.celllayout.testcases.SimpleReorderCase;
+import com.android.launcher3.tapl.Widget;
+import com.android.launcher3.tapl.WidgetResizeFrame;
+import com.android.launcher3.ui.AbstractLauncherUiTest;
+import com.android.launcher3.ui.TaplTestsLauncher3;
+import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
+import com.android.launcher3.util.rule.ShellCommandRule;
+import com.android.launcher3.views.DoubleShadowBubbleTextView;
+import com.android.launcher3.widget.LauncherAppWidgetHostView;
+
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ReorderWidgets extends AbstractLauncherUiTest {
+
+    @Rule
+    public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
+
+    private static final String TAG = ReorderWidgets.class.getSimpleName();
+
+    TestWorkspaceBuilder mWorkspaceBuilder;
+
+    private View getViewAt(int cellX, int cellY) {
+        return getFromLauncher(l -> l.getWorkspace().getScreenWithId(
+                l.getWorkspace().getScreenIdForPageIndex(0)).getChildAt(cellX, cellY));
+    }
+
+    private Point getCellDimensions() {
+        return getFromLauncher(l -> {
+            CellLayout cellLayout = l.getWorkspace().getScreenWithId(
+                    l.getWorkspace().getScreenIdForPageIndex(0));
+            return new Point(cellLayout.getWidth() / cellLayout.getCountX(),
+                    cellLayout.getHeight() / cellLayout.getCountY());
+        });
+    }
+
+    @Before
+    public void setup() throws Throwable {
+        mWorkspaceBuilder = new TestWorkspaceBuilder(this, mTargetContext);
+        TaplTestsLauncher3.initialize(this);
+        clearHomescreen();
+    }
+
+    /**
+     * Validate if the given board represent the current CellLayout
+     **/
+    private boolean validateBoard(CellLayoutBoard board) {
+        boolean match = true;
+        Point cellDimensions = getCellDimensions();
+        for (CellLayoutBoard.WidgetRect widgetRect : board.getWidgets()) {
+            if (widgetRect.shouldIgnore()) {
+                continue;
+            }
+            View widget = getViewAt(widgetRect.getCellX(), widgetRect.getCellY());
+            assertTrue("The view selected at " + board + " is not a widget",
+                    widget instanceof LauncherAppWidgetHostView);
+            match &= widgetRect.getSpanX()
+                    == Math.round(widget.getWidth() / (float) cellDimensions.x);
+            match &= widgetRect.getSpanY()
+                    == Math.round(widget.getHeight() / (float) cellDimensions.y);
+            if (!match) return match;
+        }
+        for (CellLayoutBoard.IconPoint iconPoint : board.getIcons()) {
+            View icon = getViewAt(iconPoint.getCoord().x, iconPoint.getCoord().y);
+            assertTrue("The view selected at " + iconPoint.coord + " is not an Icon",
+                    icon instanceof DoubleShadowBubbleTextView);
+        }
+        return match;
+    }
+
+    private void runTestCase(ReorderTestCase testCase)
+            throws ExecutionException, InterruptedException {
+        Point mainWidgetCellPos = testCase.mStart.getMain();
+
+        FavoriteItemsTransaction transaction =
+                new FavoriteItemsTransaction(mTargetContext, this);
+        mWorkspaceBuilder.buildFromBoard(testCase.mStart, transaction).commit();
+
+        Widget widget = mLauncher.getWorkspace().getWidgetAtCell(mainWidgetCellPos.x,
+                mainWidgetCellPos.y);
+        assertNotNull(widget);
+        WidgetResizeFrame resizeFrame = widget.dragWidgetToWorkspace(testCase.moveMainTo.x,
+                testCase.moveMainTo.y);
+        resizeFrame.dismiss();
+
+        boolean isValid = false;
+        for (CellLayoutBoard board : testCase.mEnd) {
+            isValid |= validateBoard(board);
+        }
+        assertTrue("Non of the valid boards match with the current state", isValid);
+    }
+
+    /**
+     * Run only the test define for the current grid size if such test exist
+     *
+     * @param testCaseMap map containing all the tests per grid size (Point)
+     */
+    private void runTestCaseMap(Map<Point, ReorderTestCase> testCaseMap, String testName)
+            throws ExecutionException, InterruptedException {
+        Point iconGridDimensions = mLauncher.getWorkspace().getIconGridDimensions();
+        Log.d(TAG, "Running test " + testName + " for grid " + iconGridDimensions);
+        Assume.assumeTrue(
+                "The test " + testName + " doesn't support " + iconGridDimensions + " grid layout",
+                testCaseMap.containsKey(iconGridDimensions));
+        runTestCase(testCaseMap.get(iconGridDimensions));
+    }
+
+    @ScreenRecord // b/242323136
+    @Test
+    public void simpleReorder()  throws ExecutionException, InterruptedException {
+        runTestCaseMap(SimpleReorderCase.TEST_BY_GRID_SIZE,
+                SimpleReorderCase.class.getSimpleName());
+    }
+
+    @ScreenRecord // b/242323136
+    @Test
+    public void pushTest()  throws ExecutionException, InterruptedException {
+        runTestCaseMap(PushReorderCase.TEST_BY_GRID_SIZE, PushReorderCase.class.getSimpleName());
+    }
+
+    @ScreenRecord // b/242323136
+    @Test
+    public void fullReorder()  throws ExecutionException, InterruptedException {
+        runTestCaseMap(FullReorderCase.TEST_BY_GRID_SIZE, FullReorderCase.class.getSimpleName());
+    }
+
+    @ScreenRecord // b/242323136
+    @Test
+    public void moveOutReorder()  throws ExecutionException, InterruptedException {
+        runTestCaseMap(MoveOutReorderCase.TEST_BY_GRID_SIZE,
+                MoveOutReorderCase.class.getSimpleName());
+    }
+}
diff --git a/tests/src/com/android/launcher3/celllayout/TestBoardAppIcon.java b/tests/src/com/android/launcher3/celllayout/TestBoardAppIcon.java
new file mode 100644
index 0000000..04604d7
--- /dev/null
+++ b/tests/src/com/android/launcher3/celllayout/TestBoardAppIcon.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.celllayout;
+
+import android.graphics.Point;
+
+public class TestBoardAppIcon {
+    public Point coord;
+    public char mType;
+
+    public TestBoardAppIcon(Point coord, char type) {
+        this.coord = coord;
+        mType = type;
+    }
+
+    public char getType() {
+        return mType;
+    }
+
+    public void setType(char type) {
+        mType = type;
+    }
+
+    public Point getCoord() {
+        return coord;
+    }
+
+    public void setCoord(Point coord) {
+        this.coord = coord;
+    }
+}
diff --git a/tests/src/com/android/launcher3/celllayout/TestBoardWidget.java b/tests/src/com/android/launcher3/celllayout/TestBoardWidget.java
new file mode 100644
index 0000000..7f9aa95
--- /dev/null
+++ b/tests/src/com/android/launcher3/celllayout/TestBoardWidget.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.celllayout;
+
+import android.graphics.Rect;
+
+public class TestBoardWidget {
+    public char mType;
+    public Rect mBounds;
+
+    TestBoardWidget(char type, Rect bounds) {
+        this.mType = type;
+        this.mBounds = bounds;
+    }
+
+    int getSpanX() {
+        return mBounds.right - mBounds.left + 1;
+    }
+
+    int getSpanY() {
+        return mBounds.top - mBounds.bottom + 1;
+    }
+
+    int getCellX() {
+        return mBounds.left;
+    }
+
+    int getCellY() {
+        return mBounds.bottom;
+    }
+
+    boolean shouldIgnore() {
+        return this.mType == 'x';
+    }
+}
diff --git a/tests/src/com/android/launcher3/celllayout/TestWorkspaceBuilder.java b/tests/src/com/android/launcher3/celllayout/TestWorkspaceBuilder.java
new file mode 100644
index 0000000..16448af
--- /dev/null
+++ b/tests/src/com/android/launcher3/celllayout/TestWorkspaceBuilder.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.celllayout;
+
+import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
+import static com.android.launcher3.util.WidgetUtils.createWidgetInfo;
+
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Process;
+import android.os.UserHandle;
+import android.util.Log;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.ui.AbstractLauncherUiTest;
+import com.android.launcher3.ui.TestViewHelpers;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
+
+public class TestWorkspaceBuilder {
+
+    private static final String TAG = "CellLayoutBoardBuilder";
+    private static final ComponentName APP_COMPONENT_NAME = new ComponentName(
+            "com.google.android.calculator", "com.android.calculator2.Calculator");
+
+    public AbstractLauncherUiTest mTest;
+
+    private UserHandle mMyUser;
+
+    private Context mContext;
+    private ContentResolver mResolver;
+
+    public TestWorkspaceBuilder(AbstractLauncherUiTest test, Context context) {
+        mTest = test;
+        mMyUser = Process.myUserHandle();
+        mContext = context;
+        mResolver = mContext.getContentResolver();
+    }
+
+    /**
+     * Fills the given rect in WidgetRect with 1x1 widgets. This is useful to equalize cases.
+     */
+    private FavoriteItemsTransaction fillWithWidgets(CellLayoutBoard.WidgetRect widgetRect,
+            FavoriteItemsTransaction transaction) {
+        int initX = widgetRect.getCellX();
+        int initY = widgetRect.getCellY();
+        for (int x = initX; x < initX + widgetRect.getSpanX(); x++) {
+            for (int y = initY; y < initY + widgetRect.getSpanY(); y++) {
+                try {
+                    // this widgets are filling, we don't care if we can't place them
+                    ItemInfo item = createWidgetInCell(
+                            new CellLayoutBoard.WidgetRect(CellLayoutBoard.CellType.IGNORE,
+                                    new Rect(x, y, x, y))
+                    );
+                    transaction.addItem(item);
+                } catch (Exception e) {
+                    Log.d(TAG, "Unable to place filling widget at " + x + "," + y);
+                }
+            }
+        }
+        return transaction;
+    }
+
+    private int getID() {
+        return LauncherSettings.Settings.call(
+                        mResolver, LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
+                .getInt(LauncherSettings.Settings.EXTRA_VALUE);
+    }
+
+    private AppInfo getApp() {
+        return new AppInfo(APP_COMPONENT_NAME, "test icon", mMyUser,
+                AppInfo.makeLaunchIntent(APP_COMPONENT_NAME));
+    }
+
+    private void addCorrespondingWidgetRect(CellLayoutBoard.WidgetRect widgetRect,
+            FavoriteItemsTransaction transaction) {
+        if (widgetRect.mType == 'x') {
+            fillWithWidgets(widgetRect, transaction);
+        } else {
+            transaction.addItem(createWidgetInCell(widgetRect));
+        }
+    }
+
+    /**
+     * Builds the given board into the transaction
+     */
+    public FavoriteItemsTransaction buildFromBoard(CellLayoutBoard board,
+            FavoriteItemsTransaction transaction) {
+        board.getWidgets().forEach(
+                (widgetRect) -> addCorrespondingWidgetRect(widgetRect, transaction));
+        board.getIcons().forEach((iconPoint) ->
+                transaction.addItem(createIconInCell(iconPoint))
+        );
+        return transaction;
+    }
+
+    /**
+     * Fills the hotseat row with apps instead of suggestions, for this to work the workspace should
+     * be clean otherwise this doesn't overrides the existing icons.
+     */
+    public FavoriteItemsTransaction fillHotseatIcons(FavoriteItemsTransaction transaction) {
+        int hotseatCount = InvariantDeviceProfile.INSTANCE.get(mContext).numShownHotseatIcons;
+        for (int i = 0; i < hotseatCount; i++) {
+            transaction.addItem(getHotseatValues(i));
+        }
+        return transaction;
+    }
+
+    private ItemInfo createWidgetInCell(CellLayoutBoard.WidgetRect widgetRect) {
+        LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(mTest, false);
+        LauncherAppWidgetInfo item = createWidgetInfo(info,
+                ApplicationProvider.getApplicationContext(), true);
+        item.id = getID();
+        item.cellX = widgetRect.getCellX();
+        item.cellY = widgetRect.getCellY();
+        item.spanX = widgetRect.getSpanX();
+        item.spanY = widgetRect.getSpanY();
+        item.screenId = FIRST_SCREEN_ID;
+        return item;
+    }
+
+    private ItemInfo createIconInCell(CellLayoutBoard.IconPoint iconPoint) {
+        WorkspaceItemInfo item = new WorkspaceItemInfo(getApp());
+        item.id = getID();
+        item.screenId = FIRST_SCREEN_ID;
+        item.cellX = iconPoint.getCoord().x;
+        item.cellY = iconPoint.getCoord().y;
+        item.minSpanY = item.minSpanX = item.spanX = item.spanY = 1;
+        item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
+        return item;
+    }
+
+    private ItemInfo getHotseatValues(int x) {
+        WorkspaceItemInfo item = new WorkspaceItemInfo(getApp());
+        item.id = getID();
+        item.cellX = x;
+        item.cellY = 0;
+        item.minSpanY = item.minSpanX = item.spanX = item.spanY = 1;
+        item.rank = x;
+        item.screenId = x;
+        item.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+        return item;
+    }
+}
diff --git a/tests/src/com/android/launcher3/celllayout/testcases/FullReorderCase.java b/tests/src/com/android/launcher3/celllayout/testcases/FullReorderCase.java
new file mode 100644
index 0000000..a98882c
--- /dev/null
+++ b/tests/src/com/android/launcher3/celllayout/testcases/FullReorderCase.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.celllayout.testcases;
+
+import android.graphics.Point;
+
+import java.util.Map;
+
+/**
+ * The grids represent the workspace to be build by TestWorkspaceBuilder, to see what each character
+ * in the board mean refer to {@code CellType}
+ */
+public class FullReorderCase {
+
+    /** 5x5 Test
+     **/
+    private static final String START_BOARD_STR_5x5 = ""
+            + "xxxxx\n"
+            + "222mm\n"
+            + "222mm\n"
+            + "ii111\n"
+            + "ii111";
+    private static final Point MOVE_TO_5x5 = new Point(0, 4);
+    private static final String END_BOARD_STR_5x5 = ""
+            + "xxxxx\n"
+            + "222ii\n"
+            + "222ii\n"
+            + "mm111\n"
+            + "mm111";
+    private static final ReorderTestCase TEST_CASE_5x5 = new ReorderTestCase(START_BOARD_STR_5x5,
+            MOVE_TO_5x5,
+            END_BOARD_STR_5x5);
+
+    /** 6x5 Test
+     **/
+    private static final String START_BOARD_STR_6x5 = ""
+            + "xxxxxx\n"
+            + "2222mm\n"
+            + "2222mm\n"
+            + "ii1111\n"
+            + "ii1111";
+    private static final Point MOVE_TO_6x5 = new Point(0, 4);
+    private static final String END_BOARD_STR_6x5 = ""
+            + "xxxxxx\n"
+            + "2222ii\n"
+            + "2222ii\n"
+            + "mm1111\n"
+            + "mm1111";
+    private static final ReorderTestCase TEST_CASE_6x5 = new ReorderTestCase(START_BOARD_STR_6x5,
+            MOVE_TO_6x5,
+            END_BOARD_STR_6x5);
+
+    /** 4x4 Test
+     **/
+    private static final String START_BOARD_STR_4x4 = ""
+            + "xxxx\n"
+            + "22mm\n"
+            + "iimm\n"
+            + "ii11";
+    private static final Point MOVE_TO_4x4 = new Point(0, 3);
+    private static final String END_BOARD_STR_4x4 = ""
+            + "xxxx\n"
+            + "22ii\n"
+            + "mmii\n"
+            + "mm11";
+
+    private static final ReorderTestCase TEST_CASE_4x4 = new ReorderTestCase(START_BOARD_STR_4x4,
+            MOVE_TO_4x4,
+            END_BOARD_STR_4x4);
+
+    public static final Map<Point, ReorderTestCase> TEST_BY_GRID_SIZE =
+            Map.of(new Point(5, 5), TEST_CASE_5x5,
+                    new Point(6, 5), TEST_CASE_6x5,
+                    new Point(4, 4), TEST_CASE_4x4);
+}
diff --git a/tests/src/com/android/launcher3/celllayout/testcases/MoveOutReorderCase.java b/tests/src/com/android/launcher3/celllayout/testcases/MoveOutReorderCase.java
new file mode 100644
index 0000000..a222d3d
--- /dev/null
+++ b/tests/src/com/android/launcher3/celllayout/testcases/MoveOutReorderCase.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.celllayout.testcases;
+
+import android.graphics.Point;
+
+import java.util.Map;
+
+/**
+ * The grids represent the workspace to be build by TestWorkspaceBuilder, to see what each character
+ * in the board mean refer to {@code CellType}
+ */
+public class MoveOutReorderCase {
+
+    /** 5x5 Test
+     **/
+    private static final String START_BOARD_STR_5x5 = ""
+            + "xxxxx\n"
+            + "34-m-\n"
+            + "35111\n"
+            + "32111\n"
+            + "32111";
+    private static final Point MOVE_TO_5x5 = new Point(1, 2);
+    private static final String END_BOARD_STR_5x5 = ""
+            + "xxxxx\n"
+            + "345--\n"
+            + "3m111\n"
+            + "32111\n"
+            + "32111";
+    private static final ReorderTestCase TEST_CASE_5x5 = new ReorderTestCase(START_BOARD_STR_5x5,
+            MOVE_TO_5x5,
+            END_BOARD_STR_5x5);
+
+    /** 6x5 Test
+     **/
+    private static final String START_BOARD_STR_6x5 = ""
+            + "xxxxxx\n"
+            + "34-m--\n"
+            + "351111\n"
+            + "321111\n"
+            + "321111";
+    private static final Point MOVE_TO_6x5 = new Point(1, 2);
+    private static final String END_BOARD_STR_6x5 = ""
+            + "xxxxxx\n"
+            + "345---\n"
+            + "3m1111\n"
+            + "321111\n"
+            + "321111";
+    private static final ReorderTestCase TEST_CASE_6x5 = new ReorderTestCase(START_BOARD_STR_6x5,
+            MOVE_TO_6x5,
+            END_BOARD_STR_6x5);
+
+    /** 4x4 Test
+     **/
+    private static final String START_BOARD_STR_4x4 = ""
+            + "xxxx\n"
+            + "34-m\n"
+            + "3511\n"
+            + "3211";
+    private static final Point MOVE_TO_4x4 = new Point(1, 2);
+    private static final String END_BOARD_STR_4x4 = ""
+            + "xxxx\n"
+            + "345-\n"
+            + "3m11\n"
+            + "3211";
+    private static final ReorderTestCase TEST_CASE_4x4 = new ReorderTestCase(START_BOARD_STR_4x4,
+            MOVE_TO_4x4,
+            END_BOARD_STR_4x4);
+
+    public static final Map<Point, ReorderTestCase> TEST_BY_GRID_SIZE =
+            Map.of(new Point(5, 5), TEST_CASE_5x5,
+                    new Point(6, 5), TEST_CASE_6x5,
+                    new Point(4, 4), TEST_CASE_4x4);
+}
diff --git a/tests/src/com/android/launcher3/celllayout/testcases/PushReorderCase.java b/tests/src/com/android/launcher3/celllayout/testcases/PushReorderCase.java
new file mode 100644
index 0000000..e16ff42
--- /dev/null
+++ b/tests/src/com/android/launcher3/celllayout/testcases/PushReorderCase.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.celllayout.testcases;
+
+import android.graphics.Point;
+
+import java.util.Map;
+
+/**
+ * The grids represent the workspace to be build by TestWorkspaceBuilder, to see what each character
+ * in the board mean refer to {@code CellType}
+ */
+public class PushReorderCase {
+
+    /** 5x5 Test
+     **/
+    private static final String START_BOARD_STR_5x5 = ""
+            + "xxxxx\n"
+            + "222m-\n"
+            + "--111\n"
+            + "--333\n"
+            + "-----";
+    private static final Point MOVE_TO_5x5 = new Point(2, 1);
+    private static final String END_BOARD_STR_5x5 = ""
+            + "xxxxx\n"
+            + "--m--\n"
+            + "222--\n"
+            + "--111\n"
+            + "--333";
+    private static final ReorderTestCase TEST_CASE_5x5 = new ReorderTestCase(START_BOARD_STR_5x5,
+            MOVE_TO_5x5,
+            END_BOARD_STR_5x5);
+
+
+    /** 6x5 Test
+     **/
+    private static final String START_BOARD_STR_6x5 = ""
+            + "xxxxxx\n"
+            + "2222m-\n"
+            + "--111-\n"
+            + "--333-\n"
+            + "------";
+    private static final Point MOVE_TO_6x5 = new Point(2, 1);
+    private static final String END_BOARD_STR_6x5 = ""
+            + "xxxxxx\n"
+            + "--m---\n"
+            + "2222--\n"
+            + "--111-\n"
+            + "--333-";
+    private static final ReorderTestCase TEST_CASE_6x5 = new ReorderTestCase(START_BOARD_STR_6x5,
+            MOVE_TO_6x5,
+            END_BOARD_STR_6x5);
+
+    /** 4x4 Test
+     **/
+    private static final String START_BOARD_STR_4x4 = ""
+            + "xxxx\n"
+            + "222m\n"
+            + "-111\n"
+            + "----";
+    private static final Point MOVE_TO_4x4 = new Point(2, 1);
+    private static final String END_BOARD_STR_4x4 = ""
+            + "xxxx\n"
+            + "--m-\n"
+            + "222-\n"
+            + "-111";
+    private static final ReorderTestCase TEST_CASE_4x4 = new ReorderTestCase(START_BOARD_STR_4x4,
+            MOVE_TO_4x4,
+            END_BOARD_STR_4x4);
+
+    public static final Map<Point, ReorderTestCase> TEST_BY_GRID_SIZE =
+            Map.of(new Point(5, 5), TEST_CASE_5x5,
+                    new Point(6, 5), TEST_CASE_6x5,
+                    new Point(4, 4), TEST_CASE_4x4);
+}
diff --git a/tests/src/com/android/launcher3/celllayout/testcases/ReorderTestCase.java b/tests/src/com/android/launcher3/celllayout/testcases/ReorderTestCase.java
new file mode 100644
index 0000000..0a28668
--- /dev/null
+++ b/tests/src/com/android/launcher3/celllayout/testcases/ReorderTestCase.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.celllayout.testcases;
+
+import android.graphics.Point;
+
+import com.android.launcher3.celllayout.CellLayoutBoard;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class ReorderTestCase {
+    public CellLayoutBoard mStart;
+    public Point moveMainTo;
+    public List<CellLayoutBoard> mEnd;
+
+    ReorderTestCase(CellLayoutBoard start, Point moveMainTo, CellLayoutBoard ... end) {
+        mStart = start;
+        this.moveMainTo = moveMainTo;
+        mEnd = Arrays.asList(end);
+    }
+
+    ReorderTestCase(String start, Point moveMainTo, String ... end) {
+        mStart = CellLayoutBoard.boardFromString(start);
+        this.moveMainTo = moveMainTo;
+        mEnd = Arrays
+                .asList(end)
+                .stream()
+                .map(CellLayoutBoard::boardFromString)
+                .collect(Collectors.toList());
+    }
+}
diff --git a/tests/src/com/android/launcher3/celllayout/testcases/SimpleReorderCase.java b/tests/src/com/android/launcher3/celllayout/testcases/SimpleReorderCase.java
new file mode 100644
index 0000000..546c48b
--- /dev/null
+++ b/tests/src/com/android/launcher3/celllayout/testcases/SimpleReorderCase.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.celllayout.testcases;
+
+import android.graphics.Point;
+
+import java.util.Map;
+
+/**
+ * The grids represent the workspace to be build by TestWorkspaceBuilder, to see what each character
+ * in the board mean refer to {@code CellType}
+ */
+public class SimpleReorderCase {
+
+    /** 5x5 Test
+     **/
+    private static final String START_BOARD_STR_5x5 = ""
+            + "xxxxx\n"
+            + "--mm-\n"
+            + "--mm-\n"
+            + "-----\n"
+            + "-----";
+    private static final Point MOVE_TO_5x5 = new Point(4, 4);
+    private static final String END_BOARD_STR_5x5 = ""
+            + "xxxxx\n"
+            + "-----\n"
+            + "-----\n"
+            + "---mm\n"
+            + "---mm";
+    private static final ReorderTestCase TEST_CASE_5x5 = new ReorderTestCase(START_BOARD_STR_5x5,
+            MOVE_TO_5x5,
+            END_BOARD_STR_5x5);
+
+    /** 4x4 Test
+     **/
+    private static final String START_BOARD_STR_4x4 = ""
+            + "xxxx\n"
+            + "--mm\n"
+            + "--mm\n"
+            + "----";
+    private static final Point MOVE_TO_4x4 = new Point(3, 3);
+    private static final String END_BOARD_STR_4x4 = ""
+            + "xxxx\n"
+            + "----\n"
+            + "--mm\n"
+            + "--mm";
+    private static final ReorderTestCase TEST_CASE_4x4 = new ReorderTestCase(START_BOARD_STR_4x4,
+            MOVE_TO_4x4,
+            END_BOARD_STR_4x4);
+
+    public static final Map<Point, ReorderTestCase> TEST_BY_GRID_SIZE =
+            Map.of(new Point(5, 5), TEST_CASE_5x5,
+                    new Point(4, 4), TEST_CASE_4x4);
+}
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/deviceemulator/models/DeviceEmulationData.java b/tests/src/com/android/launcher3/deviceemulator/models/DeviceEmulationData.java
index 8d275cc..55b7bf1 100644
--- a/tests/src/com/android/launcher3/deviceemulator/models/DeviceEmulationData.java
+++ b/tests/src/com/android/launcher3/deviceemulator/models/DeviceEmulationData.java
@@ -17,10 +17,10 @@
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
-import static com.android.launcher3.ResourceUtils.NAVBAR_HEIGHT;
-import static com.android.launcher3.ResourceUtils.NAVBAR_HEIGHT_LANDSCAPE;
-import static com.android.launcher3.ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE;
-import static com.android.launcher3.ResourceUtils.getDimenByName;
+import static com.android.launcher3.testing.shared.ResourceUtils.NAVBAR_HEIGHT;
+import static com.android.launcher3.testing.shared.ResourceUtils.NAVBAR_HEIGHT_LANDSCAPE;
+import static com.android.launcher3.testing.shared.ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE;
+import static com.android.launcher3.testing.shared.ResourceUtils.getDimenByName;
 
 import android.content.Context;
 import android.content.res.Resources;
diff --git a/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java b/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
index dba0a40..7d36740 100644
--- a/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
+++ b/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
@@ -1,5 +1,7 @@
 package com.android.launcher3.model;
 
+import static com.android.launcher3.testing.shared.TestProtocol.INCORRECT_INFO_UPDATED;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotSame;
@@ -14,6 +16,7 @@
 import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.util.Log;
 
 import androidx.annotation.NonNull;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -26,6 +29,7 @@
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.util.LauncherModelHelper;
 
 import org.junit.After;
@@ -43,12 +47,15 @@
 @RunWith(AndroidJUnit4.class)
 public class CacheDataUpdatedTaskTest {
 
+    private static final String TAG = "CacheDataUpdatedTaskTest";
+
     private static final String NEW_LABEL_PREFIX = "new-label-";
 
     private LauncherModelHelper mModelHelper;
 
     @Before
     public void setup() throws Exception {
+        TestProtocol.sDebugTracing = true;
         mModelHelper = new LauncherModelHelper();
         mModelHelper.initializeData("cache_data_updated_task_data");
 
@@ -57,23 +64,26 @@
         IconCache iconCache = LauncherAppState.getInstance(context).getIconCache();
         CachingLogic<ItemInfo> placeholderLogic = new CachingLogic<ItemInfo>() {
             @Override
-            public ComponentName getComponent(ItemInfo info) {
+            @NonNull
+            public ComponentName getComponent(@NonNull ItemInfo info) {
                 return info.getTargetComponent();
             }
 
+            @NonNull
             @Override
-            public UserHandle getUser(ItemInfo info) {
+            public UserHandle getUser(@NonNull ItemInfo info) {
                 return info.user;
             }
 
+            @NonNull
             @Override
-            public CharSequence getLabel(ItemInfo info) {
+            public CharSequence getLabel(@NonNull ItemInfo info) {
                 return NEW_LABEL_PREFIX + info.id;
             }
 
             @NonNull
             @Override
-            public BitmapInfo loadIcon(Context context, ItemInfo info) {
+            public BitmapInfo loadIcon(@NonNull Context context, @NonNull ItemInfo info) {
                 return BitmapInfo.of(Bitmap.createBitmap(1, 1, Config.ARGB_8888), Color.RED);
             }
         };
@@ -88,6 +98,7 @@
     @After
     public void tearDown() {
         mModelHelper.destroy();
+        TestProtocol.sDebugTracing = false;
     }
 
     private CacheDataUpdatedTask newTask(int op, String... pkg) {
@@ -111,6 +122,7 @@
         // Verify that only app1 var updated in allAppsList
         assertFalse(mModelHelper.getAllAppsList().data.isEmpty());
         for (AppInfo info : mModelHelper.getAllAppsList().data) {
+            Log.i(INCORRECT_INFO_UPDATED, "testCacheUpdate_update_apps: checking info=" + info);
             if (info.componentName.getPackageName().equals("app1")) {
                 assertFalse(info.bitmap.isNullOrLowRes());
             } else {
diff --git a/tests/src/com/android/launcher3/nonquickstep/HotseatWidthCalculationTest.kt b/tests/src/com/android/launcher3/nonquickstep/HotseatWidthCalculationTest.kt
new file mode 100644
index 0000000..55520e8
--- /dev/null
+++ b/tests/src/com/android/launcher3/nonquickstep/HotseatWidthCalculationTest.kt
@@ -0,0 +1,163 @@
+/*
+ * 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.nonquickstep
+
+import android.graphics.Rect
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.DeviceProfileBaseTest
+import com.android.launcher3.util.WindowBounds
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class HotseatWidthCalculationTest : DeviceProfileBaseTest() {
+
+    /**
+     * This is a case when after setting the hotseat, the space needs to be recalculated
+     * but it doesn't need to change QSB width or remove icons
+     */
+    @Test
+    fun distribute_border_space_when_space_is_enough_portrait() {
+        initializeVarsForTablet(isGestureMode = false)
+        windowBounds = WindowBounds(Rect(0, 0, 1800, 2560), Rect(0, 104, 0, 0))
+        val dp = newDP()
+        dp.isTaskbarPresentInApps = true
+
+        assertThat(dp.hotseatBarEndOffset).isEqualTo(0)
+        assertThat(dp.numShownHotseatIcons).isEqualTo(6)
+        assertThat(dp.hotseatBorderSpace).isEqualTo(145)
+
+        assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(177)
+        assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(177)
+
+        assertThat(dp.isQsbInline).isFalse()
+        assertThat(dp.hotseatQsbWidth).isEqualTo(1445)
+    }
+
+    /**
+     * This is a case when after setting the hotseat, and recalculating spaces
+     * it still needs to remove icons for everything to fit
+     */
+    @Test
+    fun decrease_num_of_icons_when_not_enough_space_portrait() {
+        initializeVarsForTablet(isGestureMode = false)
+        windowBounds = WindowBounds(Rect(0, 0, 1300, 2560), Rect(0, 104, 0, 0))
+        val dp = newDP()
+        dp.isTaskbarPresentInApps = true
+
+        assertThat(dp.hotseatBarEndOffset).isEqualTo(0)
+        assertThat(dp.numShownHotseatIcons).isEqualTo(6)
+        assertThat(dp.hotseatBorderSpace).isEqualTo(94)
+
+        assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(121)
+        assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(121)
+
+        assertThat(dp.isQsbInline).isFalse()
+        assertThat(dp.hotseatQsbWidth).isEqualTo(1058)
+    }
+
+    /**
+     * This is a case when after setting the hotseat, the space needs to be recalculated
+     * but it doesn't need to change QSB width or remove icons
+     */
+    @Test
+    fun distribute_border_space_when_space_is_enough_landscape() {
+        initializeVarsForTwoPanel(isGestureMode = false, isLandscape = true)
+        val dp = newDP()
+        dp.isTaskbarPresentInApps = true
+
+        assertThat(dp.hotseatBarEndOffset).isEqualTo(0)
+        assertThat(dp.numShownHotseatIcons).isEqualTo(6)
+        assertThat(dp.hotseatBorderSpace).isEqualTo(105)
+
+        assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(370)
+        assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(370)
+
+        assertThat(dp.isQsbInline).isFalse()
+        assertThat(dp.hotseatQsbWidth).isEqualTo(1467)
+    }
+
+    /**
+     * This is a case when the hotseat spans a certain amount of columns
+     * and the nav buttons push the hotseat to the side, but not enough to change the border space.
+     */
+    @Test
+    fun nav_buttons_dont_interfere_with_required_hotseat_width() {
+        initializeVarsForTablet(isGestureMode = false, isLandscape = true)
+        inv?.apply {
+            hotseatColumnSpan = IntArray(4) { 4 }
+            inlineQsb = BooleanArray(4) { false }
+        }
+        val dp = newDP()
+        dp.isTaskbarPresentInApps = true
+
+        assertThat(dp.hotseatBarEndOffset).isEqualTo(0)
+        assertThat(dp.numShownHotseatIcons).isEqualTo(6)
+        assertThat(dp.hotseatBorderSpace).isEqualTo(100)
+
+        assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(668)
+        assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(668)
+
+        assertThat(dp.isQsbInline).isFalse()
+        assertThat(dp.hotseatQsbWidth).isEqualTo(1224)
+    }
+
+    /**
+     * This is a case when after setting the hotseat, the QSB width needs to be changed to fit
+     */
+    @Test
+    fun decrease_qsb_when_not_enough_space_landscape() {
+        initializeVarsForTablet(isGestureMode = false, isLandscape = true)
+        windowBounds = WindowBounds(Rect(0, 0, 2460, 1600), Rect(0, 104, 0, 0))
+        val dp = newDP()
+        dp.isTaskbarPresentInApps = true
+
+        assertThat(dp.hotseatBarEndOffset).isEqualTo(0)
+        assertThat(dp.numShownHotseatIcons).isEqualTo(6)
+        assertThat(dp.hotseatBorderSpace).isEqualTo(96)
+
+        assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(643)
+        assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(643)
+
+        assertThat(dp.isQsbInline).isFalse()
+        assertThat(dp.hotseatQsbWidth).isEqualTo(1174)
+    }
+
+    /**
+     * This is a case when after setting the hotseat, changing QSB width, and recalculating spaces
+     * it still needs to remove icons for everything to fit
+     */
+    @Test
+    fun decrease_num_of_icons_when_not_enough_space_landscape() {
+        initializeVarsForTablet(isGestureMode = false, isLandscape = true)
+        windowBounds = WindowBounds(Rect(0, 0, 2260, 1600), Rect(0, 104, 0, 0))
+        val dp = newDP()
+        dp.isTaskbarPresentInApps = true
+
+        assertThat(dp.hotseatBarEndOffset).isEqualTo(0)
+        assertThat(dp.numShownHotseatIcons).isEqualTo(6)
+        assertThat(dp.hotseatBorderSpace).isEqualTo(89)
+
+        assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(589)
+        assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(589)
+
+        assertThat(dp.isQsbInline).isFalse()
+        assertThat(dp.hotseatQsbWidth).isEqualTo(1081)
+    }
+}
diff --git a/tests/src/com/android/launcher3/secondarydisplay/SDLauncherTest.java b/tests/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncherTest.java
similarity index 96%
rename from tests/src/com/android/launcher3/secondarydisplay/SDLauncherTest.java
rename to tests/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncherTest.java
index fd86cf1..93fa705 100644
--- a/tests/src/com/android/launcher3/secondarydisplay/SDLauncherTest.java
+++ b/tests/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncherTest.java
@@ -32,7 +32,7 @@
  */
 @MediumTest
 @RunWith(AndroidJUnit4.class)
-public class SDLauncherTest {
+public class SecondaryDisplayLauncherTest {
 
     @Before
     public void setUp() {
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 6f8b9d2..2d519d9 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -61,7 +61,7 @@
 import com.android.launcher3.tapl.LauncherInstrumentation.ContainerType;
 import com.android.launcher3.tapl.TestHelpers;
 import com.android.launcher3.testcomponent.TestCommandReceiver;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.util.LooperExecutor;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.Wait;
@@ -151,6 +151,8 @@
                     device.executeShellCommand(
                             "am dumpheap " + device.getLauncherPackageName() + " " + fileName);
                 }
+                Log.d(TAG, "Saved leak dump, the leak is still present: "
+                        + !launcher.noLeakedActivities());
                 sDumpWasGenerated = true;
                 result = "saved memory dump as an artifact";
             } catch (Throwable e) {
@@ -319,7 +321,7 @@
     /**
      * Adds {@param item} on the homescreen on the 0th screen
      */
-    protected void addItemToScreen(ItemInfo item) {
+    public void addItemToScreen(ItemInfo item) {
         WidgetUtils.addItemToScreen(item, mTargetContext);
         resetLoaderState();
 
@@ -525,7 +527,7 @@
     }
 
     protected int getAllAppsScroll(Launcher launcher) {
-        return launcher.getAppsView().getActiveRecyclerView().getCurrentScrollY();
+        return launcher.getAppsView().getActiveRecyclerView().computeVerticalScrollOffset();
     }
 
     private void checkLauncherIntegrity(
@@ -563,10 +565,13 @@
                     break;
                 }
                 case OVERVIEW: {
-                    checkLauncherStateInOverview(launcher, expectedContainerType, isStarted,
-                            isResumed);
-                    assertTrue(TestProtocol.stateOrdinalToString(ordinal),
-                            ordinal == TestProtocol.OVERVIEW_STATE_ORDINAL);
+                    verifyOverviewState(launcher, expectedContainerType, isStarted, isResumed,
+                            ordinal, TestProtocol.OVERVIEW_STATE_ORDINAL);
+                    break;
+                }
+                case SPLIT_SCREEN_SELECT: {
+                    verifyOverviewState(launcher, expectedContainerType, isStarted, isResumed,
+                            ordinal, TestProtocol.OVERVIEW_SPLIT_SELECT_ORDINAL);
                     break;
                 }
                 case TASKBAR_ALL_APPS:
@@ -632,5 +637,9 @@
         return homeAppIcon;
     }
 
-
+    private void verifyOverviewState(Launcher launcher, ContainerType expectedContainerType,
+            boolean isStarted, boolean isResumed, int ordinal, int expectedOrdinal) {
+        checkLauncherStateInOverview(launcher, expectedContainerType, isStarted, isResumed);
+        assertEquals(TestProtocol.stateOrdinalToString(ordinal), ordinal, expectedOrdinal);
+    }
 }
diff --git a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
index 8f2d528..266f0ae 100644
--- a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
+++ b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
@@ -4,7 +4,6 @@
 import android.view.Surface;
 
 import com.android.launcher3.tapl.TestHelpers;
-import com.android.launcher3.testing.TestProtocol;
 
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 8a97c6b..07bfe4c 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -18,6 +18,8 @@
 
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
+import android.platform.test.annotations.IwTest;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
@@ -25,6 +27,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 +56,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 +195,18 @@
     }
 
     @Test
+    @PortraitLandscape
+    public void testAllAppsDeadzoneForTablet() throws Exception {
+        assumeTrue(mLauncher.isTablet());
+
+        mLauncher.getWorkspace().switchToAllApps().dismissByTappingOutsideForTablet(
+                true /* tapRight */);
+        mLauncher.getWorkspace().switchToAllApps().dismissByTappingOutsideForTablet(
+                false /* tapRight */);
+    }
+
+    @IwTest(focusArea="launcher")
+    @Test
     @ScreenRecord // b/202433017
     public void testWorkspace() throws Exception {
         final Workspace workspace = mLauncher.getWorkspace();
@@ -199,9 +214,9 @@
         // 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"));
-
+        assertEquals("Initial workspace doesn't have the correct page", workspace.pagesPerScreen(),
+                workspace.getPageCount());
+        workspace.verifyWorkspaceAppIconIsGone("Chrome app was found on empty workspace", "Chrome");
         workspace.ensureWorkspaceIsScrollable();
 
         executeOnLauncher(
@@ -288,7 +303,7 @@
     }
 
     private int getWidgetsScroll(Launcher launcher) {
-        return getWidgetsView(launcher).getCurrentScrollY();
+        return getWidgetsView(launcher).computeVerticalScrollOffset();
     }
 
     private boolean isOptionsPopupVisible(Launcher launcher) {
@@ -318,6 +333,7 @@
         }
     }
 
+    @IwTest(focusArea="launcher")
     @Test
     @PortraitLandscape
     public void testDragAppIcon() throws Throwable {
@@ -376,6 +392,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 +406,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 +417,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 +442,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 +528,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 +539,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()}
@@ -540,4 +588,16 @@
     public static String getAppPackageName() {
         return getInstrumentation().getContext().getPackageName();
     }
+
+    @Test
+    public void testGetAppIconName() {
+        HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
+        allApps.freeze();
+        try {
+            HomeAppIcon icon = allApps.getAppIcon(APP_NAME);
+            assertEquals("Wrong app icon name.", icon.getIconName(), APP_NAME);
+        } finally {
+            allApps.unfreeze();
+        }
+    }
 }
diff --git a/tests/src/com/android/launcher3/ui/WorkProfileTest.java b/tests/src/com/android/launcher3/ui/WorkProfileTest.java
index 35b4ca6..302bd2f 100644
--- a/tests/src/com/android/launcher3/ui/WorkProfileTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkProfileTest.java
@@ -21,6 +21,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import android.util.Log;
 import android.view.View;
@@ -30,7 +31,6 @@
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.ActivityAllAppsContainerView;
 import com.android.launcher3.allapps.AllAppsPagedView;
-import com.android.launcher3.allapps.WorkAdapterProvider;
 import com.android.launcher3.allapps.WorkEduCard;
 import com.android.launcher3.allapps.WorkPausedCard;
 import com.android.launcher3.allapps.WorkProfileManager;
@@ -48,6 +48,8 @@
     private static final int WORK_PAGE = ActivityAllAppsContainerView.AdapterHolder.WORK;
 
     private int mProfileUserId;
+    private boolean mWorkProfileSetupSuccessful;
+    private final String TAG = "WorkProfileTest";
 
     @Before
     @Override
@@ -56,12 +58,17 @@
         String output =
                 mDevice.executeShellCommand(
                         "pm create-user --profileOf 0 --managed TestProfile");
-        Log.d("b/203817455", "pm create-user; output: " + output);
-        assertTrue("Failed to create work profile", output.startsWith("Success"));
+        // b/203817455
+        updateWorkProfileSetupSuccessful("pm create-user", output);
 
         String[] tokens = output.split("\\s+");
         mProfileUserId = Integer.parseInt(tokens[tokens.length - 1]);
-        mDevice.executeShellCommand("am start-user " + mProfileUserId);
+        output = mDevice.executeShellCommand("am start-user " + mProfileUserId);
+        updateWorkProfileSetupSuccessful("am start-user", output);
+
+        if (!mWorkProfileSetupSuccessful) {
+            return; // no need to setup launcher since all tests will skip.
+        }
 
         mDevice.pressHome();
         waitForLauncherCondition("Launcher didn't start", Objects::nonNull);
@@ -100,6 +107,8 @@
 
     @Test
     public void workTabExists() {
+        assumeTrue(mWorkProfileSetupSuccessful);
+        waitForWorkTabSetup();
         waitForLauncherCondition("Personal tab is missing",
                 launcher -> launcher.getAppsView().isPersonalTabVisible(),
                 LauncherInstrumentation.WAIT_TIME_MS);
@@ -110,8 +119,8 @@
 
     @Test
     public void toggleWorks() {
+        assumeTrue(mWorkProfileSetupSuccessful);
         waitForWorkTabSetup();
-
         executeOnLauncher(launcher -> {
             AllAppsPagedView pagedView = (AllAppsPagedView) launcher.getAppsView().getContentView();
             pagedView.setCurrentPage(WORK_PAGE);
@@ -127,7 +136,11 @@
                 LauncherInstrumentation.WAIT_TIME_MS);
 
         //start work profile toggle OFF test
-        executeOnLauncher(l -> l.getAppsView().getWorkManager().getWorkModeSwitch().performClick());
+        executeOnLauncher(l -> {
+            // Ensure updates are not deferred so notification happens when apps pause.
+            l.getAppsView().getAppsStore().disableDeferUpdates(DEFER_UPDATES_TEST);
+            l.getAppsView().getWorkManager().getWorkModeSwitch().performClick();
+        });
 
         waitForLauncherCondition("Work profile toggle OFF failed", launcher -> {
             manager.reset(); // pulls current state from system
@@ -153,9 +166,10 @@
 
     @Test
     public void testEdu() {
+        assumeTrue(mWorkProfileSetupSuccessful);
         waitForWorkTabSetup();
         executeOnLauncher(l -> {
-            l.getSharedPrefs().edit().putInt(WorkAdapterProvider.KEY_WORK_EDU_STEP, 0).commit();
+            l.getSharedPrefs().edit().putInt(WorkProfileManager.KEY_WORK_EDU_STEP, 0).commit();
             ((AllAppsPagedView) l.getAppsView().getContentView()).setCurrentPage(WORK_PAGE);
             l.getAppsView().getWorkManager().reset();
         });
@@ -175,4 +189,14 @@
             }
         }, LauncherInstrumentation.WAIT_TIME_MS);
     }
+
+    private void updateWorkProfileSetupSuccessful(String cli, String output) {
+        Log.d(TAG, "updateWorkProfileSetupSuccessful, cli=" + cli + " " + "output=" + output);
+        if (output.startsWith("Success")) {
+            assertTrue(output, output.startsWith("Success"));
+            mWorkProfileSetupSuccessful = true;
+        } else {
+            mWorkProfileSetupSuccessful = false;
+        }
+    }
 }
diff --git a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
index 194ee4f..561f3cc 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
@@ -20,6 +20,8 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import android.platform.test.annotations.IwTest;
+
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -45,6 +47,7 @@
     @Rule
     public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
 
+    @IwTest(focusArea="launcher")
     @Test
     @PortraitLandscape
     public void testDragIcon() throws Throwable {
diff --git a/tests/src/com/android/launcher3/util/LauncherModelHelper.java b/tests/src/com/android/launcher3/util/LauncherModelHelper.java
index 3324959..e7e551f 100644
--- a/tests/src/com/android/launcher3/util/LauncherModelHelper.java
+++ b/tests/src/com/android/launcher3/util/LauncherModelHelper.java
@@ -47,6 +47,7 @@
 import android.test.mock.MockContentResolver;
 import android.util.ArrayMap;
 
+import androidx.annotation.NonNull;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.uiautomator.UiDevice;
 
@@ -194,8 +195,9 @@
         Executor mockExecutor = mock(Executor.class);
         model.enqueueModelUpdateTask(new ModelUpdateTask() {
             @Override
-            public void init(LauncherAppState app, LauncherModel model, BgDataModel dataModel,
-                    AllAppsList allAppsList, Executor uiExecutor) {
+            public void init(@NonNull final LauncherAppState app,
+                    @NonNull final LauncherModel model, @NonNull final BgDataModel dataModel,
+                    @NonNull final AllAppsList allAppsList, @NonNull final Executor uiExecutor) {
                 task.init(app, model, dataModel, allAppsList, mockExecutor);
             }
 
diff --git a/tests/src/com/android/launcher3/util/MultiAdditivePropertyTest.kt b/tests/src/com/android/launcher3/util/MultiPropertyFactoryTest.kt
similarity index 75%
rename from tests/src/com/android/launcher3/util/MultiAdditivePropertyTest.kt
rename to tests/src/com/android/launcher3/util/MultiPropertyFactoryTest.kt
index 309d055..a4f189c 100644
--- a/tests/src/com/android/launcher3/util/MultiAdditivePropertyTest.kt
+++ b/tests/src/com/android/launcher3/util/MultiPropertyFactoryTest.kt
@@ -16,26 +16,32 @@
 
 package com.android.launcher3.util
 
-import android.view.View
+import android.util.FloatProperty
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
 
-/** Unit tests for [MultiAdditivePropertyFactory] */
+/** Unit tests for [MultiPropertyFactory] */
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-class MultiAdditivePropertyTest {
+class MultiPropertyFactoryTest {
 
     private val received = mutableListOf<Float>()
 
-    private val factory =
-        object : MultiAdditivePropertyFactory<View?>("Test", View.TRANSLATION_X) {
-            override fun apply(obj: View?, value: Float) {
-                received.add(value)
-            }
+    private val receiveProperty: FloatProperty<Any> = object : FloatProperty<Any>("receive") {
+        override fun setValue(obj: Any?, value: Float) {
+            received.add(value)
         }
+        override fun get(o: Any): Float {
+            return 0f
+        }
+    }
+
+    private val factory = MultiPropertyFactory("depth_property", receiveProperty) {
+        x: Float, y: Float -> x + y
+    }
 
     private val p1 = factory.get(1)
     private val p2 = factory.get(2)
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/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index bfb115d..4791846 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -29,7 +29,7 @@
 import androidx.test.uiautomator.StaleObjectException;
 import androidx.test.uiautomator.UiObject2;
 
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 
 import java.util.stream.Collectors;
 
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIcon.java b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
index e28f0af..2687b28 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
@@ -18,11 +18,12 @@
 
 import android.widget.TextView;
 
+import androidx.annotation.NonNull;
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.BySelector;
 import androidx.test.uiautomator.UiObject2;
 
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 
 import java.util.regex.Pattern;
 
@@ -86,4 +87,10 @@
     protected String launchableType() {
         return "app icon";
     }
+
+    /** Return the app name of a icon */
+    @NonNull
+    public String getIconName() {
+        return getObject().getText();
+    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java b/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java
index 5cf5aba..284168b 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java
@@ -18,7 +18,7 @@
 
 import androidx.test.uiautomator.UiObject2;
 
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 
 /**
  * Menu item in an app icon menu.
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index 589e13c..eb8d055 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -19,7 +19,7 @@
 import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
 
 import static com.android.launcher3.tapl.OverviewTask.TASK_START_EVENT;
-import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
+import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_STATE_ORDINAL;
 
 import android.graphics.Point;
 import android.os.SystemClock;
@@ -28,7 +28,7 @@
 import androidx.annotation.NonNull;
 import androidx.test.uiautomator.UiObject2;
 
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 
 import java.util.List;
 import java.util.regex.Pattern;
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index b7bca02..f47f710 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -19,6 +19,7 @@
 import android.graphics.Rect;
 
 import androidx.annotation.NonNull;
+import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.BySelector;
 import androidx.test.uiautomator.Direction;
 import androidx.test.uiautomator.UiObject2;
@@ -168,6 +169,27 @@
         return new OverviewTask(mLauncher, widestTask, this);
     }
 
+    /** Returns an overview task matching TestActivity {@param activityNumber}. */
+    @NonNull
+    public OverviewTask getTestActivityTask(int activityNumber) {
+        final List<UiObject2> taskViews = getTasks();
+        mLauncher.assertNotEquals("Unable to find a task", 0, taskViews.size());
+
+        final String activityName = "TestActivity" + activityNumber;
+        UiObject2 task = null;
+        for (UiObject2 taskView : taskViews) {
+            // TODO(b/239452415): Use equals instead of descEndsWith
+            if (taskView.getParent().hasObject(By.descEndsWith(activityName))) {
+                task = taskView;
+                break;
+            }
+        }
+        mLauncher.assertNotNull(
+                "Unable to find a task with " + activityName + " from the task list", task);
+
+        return new OverviewTask(mLauncher, task, this);
+    }
+
     /**
      * Returns a list of all tasks fully visible in the tablet grid overview.
      */
@@ -225,8 +247,16 @@
         return mLauncher.hasLauncherObject(mLauncher.getOverviewObjectSelector("clear_all"));
     }
 
+    protected boolean isActionsViewVisible() {
+        OverviewTask task = mLauncher.isTablet() ? getFocusedTaskForTablet() : getCurrentTask();
+        if (task == null) {
+            return false;
+        }
+        return !task.isTaskSplit();
+    }
+
     private void verifyActionsViewVisibility() {
-        if (!hasTasks()) {
+        if (!hasTasks() || !isActionsViewVisible()) {
             return;
         }
         try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
@@ -243,13 +273,11 @@
      * Returns if focused task is currently snapped task in tablet grid overview.
      */
     private boolean isOverviewSnappedToFocusedTaskForTablet() {
-        UiObject2 focusedTask = getFocusedTaskForTablet();
+        OverviewTask focusedTask = getFocusedTaskForTablet();
         if (focusedTask == null) {
             return false;
         }
-        return Math.abs(
-                focusedTask.getVisibleBounds().exactCenterX() - mLauncher.getExactScreenCenterX())
-                < 1;
+        return Math.abs(focusedTask.getExactCenterX() - mLauncher.getExactScreenCenterX()) < 1;
     }
 
     /**
@@ -257,7 +285,7 @@
      *
      * @throws IllegalStateException if not run on a tablet device.
      */
-    UiObject2 getFocusedTaskForTablet() {
+    OverviewTask getFocusedTaskForTablet() {
         if (!mLauncher.isTablet()) {
             throw new IllegalStateException("Must be run on tablet device.");
         }
@@ -268,9 +296,9 @@
         int focusedTaskHeight = mLauncher.getFocusedTaskHeightForTablet();
         for (UiObject2 task : taskViews) {
             if (task.getVisibleBounds().height() == focusedTaskHeight) {
-                return task;
+                return new OverviewTask(mLauncher, task, this);
             }
         }
         return null;
     }
-}
\ No newline at end of file
+}
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/FolderIcon.java b/tests/tapl/com/android/launcher3/tapl/FolderIcon.java
index 9b4717f..0c453bd 100644
--- a/tests/tapl/com/android/launcher3/tapl/FolderIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/FolderIcon.java
@@ -21,7 +21,7 @@
 import androidx.annotation.NonNull;
 import androidx.test.uiautomator.UiObject2;
 
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 
 /**
  * Folder Icon, an app folder in workspace.
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/HomeQsb.java b/tests/tapl/com/android/launcher3/tapl/HomeQsb.java
index 5f92199..c365708 100644
--- a/tests/tapl/com/android/launcher3/tapl/HomeQsb.java
+++ b/tests/tapl/com/android/launcher3/tapl/HomeQsb.java
@@ -15,12 +15,21 @@
  */
 package com.android.launcher3.tapl;
 
+import androidx.annotation.NonNull;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.Until;
+
 /**
  * Operations on home screen qsb.
  */
 public class HomeQsb {
 
     private final LauncherInstrumentation mLauncher;
+    private static final String ASSISTANT_APP_PACKAGE = "com.google.android.googlequicksearchbox";
+    private static final String ASSISTANT_ICON_RES_ID = "mic_icon";
+
 
     HomeQsb(LauncherInstrumentation launcher) {
         mLauncher = launcher;
@@ -28,6 +37,35 @@
     }
 
     /**
+     * Launch assistant app by tapping mic icon on qsb.
+     */
+    @NonNull
+    public LaunchedAppState launchAssistant() {
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "want to click assistant mic icon button");
+             LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+            UiObject2 assistantIcon = mLauncher.waitForLauncherObject(ASSISTANT_ICON_RES_ID);
+
+            LauncherInstrumentation.log("HomeQsb.launchAssistant before click "
+                    + assistantIcon.getVisibleCenter() + " in "
+                    + mLauncher.getVisibleBounds(assistantIcon));
+
+            mLauncher.clickLauncherObject(assistantIcon);
+
+            try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer("clicked")) {
+                // assert Assistant App Launched
+                BySelector selector = By.pkg(ASSISTANT_APP_PACKAGE);
+                mLauncher.assertTrue(
+                        "assistant app didn't start: (" + selector + ")",
+                        mLauncher.getDevice().wait(Until.hasObject(selector),
+                                LauncherInstrumentation.WAIT_TIME_MS)
+                );
+                return new LaunchedAppState(mLauncher);
+            }
+        }
+    }
+
+    /**
      * Show search result page from tapping qsb.
      */
     public SearchResultFromQsb showSearchResult() {
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index 33fea2d..3dcb437 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -16,7 +16,7 @@
 
 package com.android.launcher3.tapl;
 
-import static com.android.launcher3.testing.TestProtocol.SPRING_LOADED_STATE_ORDINAL;
+import static com.android.launcher3.testing.shared.TestProtocol.SPRING_LOADED_STATE_ORDINAL;
 
 import android.graphics.Point;
 import android.view.MotionEvent;
@@ -26,7 +26,7 @@
 import androidx.test.uiautomator.UiObject2;
 import androidx.test.uiautomator.Until;
 
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 
 /**
  * Ancestor for AppIcon and AppMenuItem.
diff --git a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
index 046d36b..a17651b 100644
--- a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
+++ b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
@@ -16,9 +16,10 @@
 
 package com.android.launcher3.tapl;
 
-import static com.android.launcher3.testing.TestProtocol.REQUEST_DISABLE_MANUAL_TASKBAR_STASHING;
-import static com.android.launcher3.testing.TestProtocol.REQUEST_ENABLE_MANUAL_TASKBAR_STASHING;
-import static com.android.launcher3.testing.TestProtocol.REQUEST_STASHED_TASKBAR_HEIGHT;
+import static com.android.launcher3.tapl.LauncherInstrumentation.TASKBAR_RES_ID;
+import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_DISABLE_MANUAL_TASKBAR_STASHING;
+import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_ENABLE_MANUAL_TASKBAR_STASHING;
+import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_STASHED_TASKBAR_HEIGHT;
 
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -27,7 +28,7 @@
 
 import androidx.test.uiautomator.By;
 
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 
 /**
  * Background state operations specific to when an app has been launched.
@@ -54,13 +55,23 @@
     public Taskbar getTaskbar() {
         try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                 "want to get the taskbar")) {
-            mLauncher.waitForLauncherObject("taskbar_view");
+            mLauncher.waitForLauncherObject(TASKBAR_RES_ID);
 
             return new Taskbar(mLauncher);
         }
     }
 
     /**
+     * Waits for the taskbar to be hidden, or fails.
+     */
+    public void assertTaskbarHidden() {
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "waiting for taskbar to be hidden")) {
+            mLauncher.waitUntilLauncherObjectGone(TASKBAR_RES_ID);
+        }
+    }
+
+    /**
      * Returns the Taskbar in a visible state.
      *
      * The taskbar must already be hidden when calling this method.
@@ -71,7 +82,7 @@
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
              LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
                      "want to show the taskbar")) {
-            mLauncher.waitUntilLauncherObjectGone("taskbar_view");
+            mLauncher.waitUntilLauncherObjectGone(TASKBAR_RES_ID);
 
             final long downTime = SystemClock.uptimeMillis();
             final int unstashTargetY = mLauncher.getRealDisplaySize().y
@@ -85,7 +96,7 @@
             LauncherInstrumentation.log("showTaskbar: sent down");
 
             try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer("pressed down")) {
-                mLauncher.waitForLauncherObject("taskbar_view");
+                mLauncher.waitForLauncherObject(TASKBAR_RES_ID);
                 mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_UP, unstashTarget,
                         LauncherInstrumentation.GestureScope.OUTSIDE_WITH_PILFER);
 
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 9d25b1b..3986df6 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -23,7 +23,7 @@
 
 import static com.android.launcher3.tapl.Folder.FOLDER_CONTENT_RES_ID;
 import static com.android.launcher3.tapl.TestHelpers.getOverviewPackageName;
-import static com.android.launcher3.testing.TestProtocol.NORMAL_STATE_ORDINAL;
+import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
 
 import android.app.ActivityManager;
 import android.app.Instrumentation;
@@ -65,10 +65,9 @@
 import androidx.test.uiautomator.UiObject2;
 import androidx.test.uiautomator.Until;
 
-import com.android.launcher3.ResourceUtils;
-import com.android.launcher3.testing.TestInformationRequest;
-import com.android.launcher3.testing.TestProtocol;
-import com.android.systemui.shared.system.ContextUtils;
+import com.android.launcher3.testing.shared.ResourceUtils;
+import com.android.launcher3.testing.shared.TestInformationRequest;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.systemui.shared.system.QuickStepContract;
 
 import org.junit.Assert;
@@ -84,7 +83,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 +99,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");
@@ -113,8 +110,11 @@
     static final Pattern EVENT_TOUCH_UP_TIS = getTouchEventPatternTIS("ACTION_UP");
     static final Pattern EVENT_TOUCH_CANCEL_TIS = getTouchEventPatternTIS("ACTION_CANCEL");
 
-    static final Pattern EVENT_KEY_BACK_DOWN = getKeyEventPattern("ACTION_DOWN", "KEYCODE_BACK");
-    static final Pattern EVENT_KEY_BACK_UP = getKeyEventPattern("ACTION_UP", "KEYCODE_BACK");
+    private static final Pattern EVENT_KEY_BACK_DOWN =
+            getKeyEventPattern("ACTION_DOWN", "KEYCODE_BACK");
+    private static final Pattern EVENT_KEY_BACK_UP =
+            getKeyEventPattern("ACTION_UP", "KEYCODE_BACK");
+    private static final Pattern EVENT_ON_BACK_INVOKED = Pattern.compile("onBackInvoked");
 
     private final String mLauncherPackage;
     private Boolean mIsLauncher3;
@@ -123,8 +123,8 @@
     // Types for launcher containers that the user is interacting with. "Background" is a
     // pseudo-container corresponding to inactive launcher covered by another app.
     public enum ContainerType {
-        WORKSPACE, HOME_ALL_APPS, OVERVIEW, WIDGETS, FALLBACK_OVERVIEW, LAUNCHED_APP,
-        TASKBAR_ALL_APPS
+        WORKSPACE, HOME_ALL_APPS, OVERVIEW, SPLIT_SCREEN_SELECT, WIDGETS, FALLBACK_OVERVIEW,
+        LAUNCHED_APP, TASKBAR_ALL_APPS
     }
 
     public enum NavigationModel {ZERO_BUTTON, THREE_BUTTON}
@@ -170,8 +170,9 @@
     private static final String OVERVIEW_RES_ID = "overview_panel";
     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;
+    static final String TASKBAR_RES_ID = "taskbar_view";
+    private static final String SPLIT_PLACEHOLDER_RES_ID = "split_placeholder";
+    public static final int WAIT_TIME_MS = 30000;
     private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
     private static final String ANDROID_PACKAGE = "android";
 
@@ -238,7 +239,7 @@
         // Launcher package. As during inproc tests the tested launcher may not be selected as the
         // current launcher, choosing target package for inproc. For out-of-proc, use the installed
         // launcher package.
-        mLauncherPackage = testPackage.equals(targetPackage)
+        mLauncherPackage = testPackage.equals(targetPackage) || isGradleInstrumentation()
                 ? getLauncherPackageName()
                 : targetPackage;
 
@@ -264,7 +265,7 @@
                 SystemClock.sleep(5000);
             } else {
                 try {
-                    final int userId = ContextUtils.getUserId(getContext());
+                    final int userId = getContext().getUserId();
                     final String launcherPidCommand = "pidof " + pi.packageName;
                     final String initialPid = mDevice.executeShellCommand(launcherPidCommand)
                             .replaceAll("\\s", "");
@@ -285,6 +286,20 @@
         }
     }
 
+    /**
+     * Gradle only supports out of process instrumentation. The test package is automatically
+     * generated by appending `.test` to the target package.
+     */
+    private boolean isGradleInstrumentation() {
+        final String testPackage = getContext().getPackageName();
+        final String targetPackage = mInstrumentation.getTargetContext().getPackageName();
+        final String testSuffix = ".test";
+
+        return testPackage.endsWith(testSuffix) && testPackage.length() > testSuffix.length()
+            && testPackage.substring(0, testPackage.length() - testSuffix.length())
+            .equals(targetPackage);
+    }
+
     public void enableCheckEventsForSuccessfulGestures() {
         mCheckEventsForSuccessfulGestures = true;
     }
@@ -362,10 +377,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));
     }
@@ -379,6 +390,17 @@
         sActiveContainer = new WeakReference<>(container);
     }
 
+    /**
+     * Sets the accesibility interactive timeout to be effectively indefinite (UI using this
+     * accesibility timeout will not automatically dismiss if true).
+     */
+    void setIndefiniteAccessibilityInteractiveUiTimeout(boolean indefiniteTimeout) {
+        final String cmd = indefiniteTimeout
+                ? "settings put secure accessibility_interactive_ui_timeout_ms 10000"
+                : "settings delete secure accessibility_interactive_ui_timeout_ms";
+        logShellCommand(cmd);
+    }
+
     public NavigationModel getNavigationModel() {
         final Context baseContext = mInstrumentation.getTargetContext();
         try {
@@ -719,6 +741,7 @@
                     waitUntilLauncherObjectGone(OVERVIEW_RES_ID);
                     waitUntilLauncherObjectGone(WIDGETS_RES_ID);
                     waitUntilLauncherObjectGone(TASKBAR_RES_ID);
+                    waitUntilLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
 
                     return waitForLauncherObject(WORKSPACE_RES_ID);
                 }
@@ -727,6 +750,7 @@
                     waitUntilLauncherObjectGone(APPS_RES_ID);
                     waitUntilLauncherObjectGone(OVERVIEW_RES_ID);
                     waitUntilLauncherObjectGone(TASKBAR_RES_ID);
+                    waitUntilLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
 
                     return waitForLauncherObject(WIDGETS_RES_ID);
                 }
@@ -736,6 +760,7 @@
                     waitUntilLauncherObjectGone(OVERVIEW_RES_ID);
                     waitUntilLauncherObjectGone(WIDGETS_RES_ID);
                     waitUntilLauncherObjectGone(TASKBAR_RES_ID);
+                    waitUntilLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
 
                     return waitForLauncherObject(APPS_RES_ID);
                 }
@@ -744,14 +769,25 @@
                     waitUntilLauncherObjectGone(WORKSPACE_RES_ID);
                     waitUntilLauncherObjectGone(WIDGETS_RES_ID);
                     waitUntilLauncherObjectGone(TASKBAR_RES_ID);
+                    waitUntilLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
 
                     return waitForLauncherObject(OVERVIEW_RES_ID);
                 }
+                case SPLIT_SCREEN_SELECT: {
+                    waitUntilLauncherObjectGone(APPS_RES_ID);
+                    waitUntilLauncherObjectGone(WORKSPACE_RES_ID);
+                    waitUntilLauncherObjectGone(WIDGETS_RES_ID);
+                    waitUntilLauncherObjectGone(TASKBAR_RES_ID);
+
+                    waitForLauncherObject(SPLIT_PLACEHOLDER_RES_ID);
+                    return waitForLauncherObject(OVERVIEW_RES_ID);
+                }
                 case FALLBACK_OVERVIEW: {
                     waitUntilLauncherObjectGone(APPS_RES_ID);
                     waitUntilLauncherObjectGone(WORKSPACE_RES_ID);
                     waitUntilLauncherObjectGone(WIDGETS_RES_ID);
                     waitUntilLauncherObjectGone(TASKBAR_RES_ID);
+                    waitUntilLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
 
                     return waitForFallbackLauncherObject(OVERVIEW_RES_ID);
                 }
@@ -760,6 +796,7 @@
                     waitUntilLauncherObjectGone(APPS_RES_ID);
                     waitUntilLauncherObjectGone(OVERVIEW_RES_ID);
                     waitUntilLauncherObjectGone(WIDGETS_RES_ID);
+                    waitUntilLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
 
                     if (isTablet() && !isFallbackOverview()) {
                         waitForLauncherObject(TASKBAR_RES_ID);
@@ -870,7 +907,14 @@
     }
 
     /**
-     * Presses nav bar home button.
+     * Goes to home by swiping up in zero-button mode or pressing Home button.
+     * Calling it after another TAPL call is safe because all TAPL methods wait for the animations
+     * to finish.
+     * When calling it after a non-TAPL method, make sure that all animations have already
+     * completed, otherwise it may detect the current state (for example "Application" or "Home")
+     * incorrectly.
+     * The method expects either app or Launcher to be active when it's called. Other states, such
+     * as visible notification shade are not supported.
      *
      * @return the Workspace object.
      */
@@ -886,7 +930,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
@@ -964,8 +1007,12 @@
                 }
             }
             if (launcherVisible) {
-                expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_KEY_BACK_DOWN);
-                expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_KEY_BACK_UP);
+                if (getContext().getApplicationInfo().isOnBackInvokedCallbackEnabled()) {
+                    expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ON_BACK_INVOKED);
+                } else {
+                    expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_KEY_BACK_DOWN);
+                    expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_KEY_BACK_UP);
+                }
             }
         }
     }
@@ -1087,6 +1134,14 @@
     }
 
     @NonNull
+    UiObject2 waitForSystemUiObject(BySelector selector) {
+        final UiObject2 object = TestHelpers.wait(
+                Until.findObject(selector), WAIT_TIME_MS);
+        assertNotNull("Can't find a systemui object with selector: " + selector, object);
+        return object;
+    }
+
+    @NonNull
     UiObject2 waitForNavigationUiObject(String resId) {
         String resPackage = getNavigationButtonResPackage();
         final UiObject2 object = mDevice.wait(
@@ -1096,6 +1151,16 @@
     }
 
     @Nullable
+    UiObject2 findObjectInContainer(UiObject2 container, String resName) {
+        try {
+            return container.findObject(getLauncherObjectSelector(resName));
+        } catch (StaleObjectException e) {
+            fail("The container disappeared from screen");
+            return null;
+        }
+    }
+
+    @Nullable
     UiObject2 findObjectInContainer(UiObject2 container, BySelector selector) {
         try {
             return container.findObject(selector);
@@ -1217,6 +1282,13 @@
         return object;
     }
 
+    @NonNull
+    List<UiObject2> waitForObjectsBySelector(BySelector selector) {
+        final List<UiObject2> objects = mDevice.wait(Until.findObjects(selector), WAIT_TIME_MS);
+        assertNotNull("Can't find any view in Launcher, selector: " + selector, objects);
+        return objects;
+    }
+
     private UiObject2 waitForObjectBySelector(BySelector selector) {
         final UiObject2 object = mDevice.wait(Until.findObject(selector), WAIT_TIME_MS);
         assertNotNull("Can't find a view in Launcher, selector: " + selector, object);
@@ -1710,6 +1782,15 @@
         getTestInfo(TestProtocol.REQUEST_UNSTASH_TASKBAR_IF_STASHED);
     }
 
+    /**
+     * Recreates the taskbar (outside of tests this is done for certain configuration changes).
+     * The expected behavior is that the taskbar retains its current state after being recreated.
+     * For example, if taskbar is currently stashed, it should still be stashed after recreating.
+     */
+    public void recreateTaskbar() {
+        getTestInfo(TestProtocol.REQUEST_RECREATE_TASKBAR);
+    }
+
     public List<String> getHotseatIconNames() {
         return getTestInfo(TestProtocol.REQUEST_HOTSEAT_ICON_NAMES)
                 .getStringArrayList(TestProtocol.TEST_INFO_RESPONSE_FIELD);
@@ -1831,4 +1912,27 @@
         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/LogEventChecker.java b/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java
index 710e3cd..672c6e0 100644
--- a/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java
+++ b/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java
@@ -15,13 +15,13 @@
  */
 package com.android.launcher3.tapl;
 
-import static com.android.launcher3.testing.TestProtocol.SEQUENCE_MAIN;
-import static com.android.launcher3.testing.TestProtocol.SEQUENCE_PILFER;
-import static com.android.launcher3.testing.TestProtocol.SEQUENCE_TIS;
+import static com.android.launcher3.testing.shared.TestProtocol.SEQUENCE_MAIN;
+import static com.android.launcher3.testing.shared.TestProtocol.SEQUENCE_PILFER;
+import static com.android.launcher3.testing.shared.TestProtocol.SEQUENCE_TIS;
 
 import android.os.SystemClock;
 
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 
 import java.util.ArrayList;
 import java.util.HashMap;
diff --git a/tests/tapl/com/android/launcher3/tapl/Overview.java b/tests/tapl/com/android/launcher3/tapl/Overview.java
index 66a51a5..50c2136 100644
--- a/tests/tapl/com/android/launcher3/tapl/Overview.java
+++ b/tests/tapl/com/android/launcher3/tapl/Overview.java
@@ -21,7 +21,7 @@
 /**
  * Overview pane.
  */
-public final class Overview extends BaseOverview {
+public class Overview extends BaseOverview {
 
     Overview(LauncherInstrumentation launcher) {
         super(launcher);
@@ -29,7 +29,7 @@
 
     @Override
     protected ContainerType getContainerType() {
-        return LauncherInstrumentation.ContainerType.OVERVIEW;
+        return ContainerType.OVERVIEW;
     }
 
     @Override
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewActions.java b/tests/tapl/com/android/launcher3/tapl/OverviewActions.java
index 2f44bb6..386deac 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewActions.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewActions.java
@@ -19,7 +19,7 @@
 import androidx.annotation.NonNull;
 import androidx.test.uiautomator.UiObject2;
 
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 
 /**
  * View containing overview actions
@@ -41,6 +41,8 @@
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                      "want to click screenshot button and exit screenshot ui")) {
+            mLauncher.setIndefiniteAccessibilityInteractiveUiTimeout(true);
+
             UiObject2 screenshot = mLauncher.waitForObjectInContainer(mOverviewActions,
                     "action_screenshot");
 
@@ -62,6 +64,8 @@
                     return new Overview(mLauncher);
                 }
             }
+        } finally {
+            mLauncher.setIndefiniteAccessibilityInteractiveUiTimeout(false);
         }
     }
 
@@ -82,7 +86,6 @@
                     "clicked select button")) {
                 return getSelectModeButtons();
             }
-
         }
     }
 
@@ -99,4 +102,22 @@
             return new SelectModeButtons(selectModeButtons, mLauncher);
         }
     }
+
+    /**
+     * Clicks split button and enters split select mode.
+     */
+    @NonNull
+    public SplitScreenSelect clickSplit() {
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+             LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                     "want to click split button to enter split select mode")) {
+            UiObject2 split = mLauncher.waitForObjectInContainer(mOverviewActions,
+                    "action_split");
+            mLauncher.clickLauncherObject(split);
+            try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer(
+                    "clicked split")) {
+                return new SplitScreenSelect(mLauncher);
+            }
+        }
+    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index c8caa42..adc993d 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -20,9 +20,12 @@
 
 import android.graphics.Rect;
 
+import androidx.annotation.NonNull;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
 import androidx.test.uiautomator.UiObject2;
 
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 
 import java.util.List;
 import java.util.regex.Pattern;
@@ -32,8 +35,12 @@
  * A recent task in the overview panel carousel.
  */
 public final class OverviewTask {
+    private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
+
     static final Pattern TASK_START_EVENT =
             Pattern.compile("startActivityFromRecentsAsync");
+    static final Pattern SPLIT_START_EVENT =
+            Pattern.compile("launchSplitTasks");
     private final LauncherInstrumentation mLauncher;
     private final UiObject2 mTask;
     private final BaseOverview mOverview;
@@ -61,6 +68,10 @@
         return mTask.getVisibleCenter().x;
     }
 
+    float getExactCenterX() {
+        return mTask.getVisibleBounds().exactCenterX();
+    }
+
     /**
      * Dismisses the task by swiping up.
      */
@@ -125,7 +136,7 @@
     }
 
     /**
-     * Clicks at the task.
+     * Clicks the task.
      */
     public LaunchedAppState open() {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
@@ -136,8 +147,41 @@
                     () -> "Launching task didn't open a new window: "
                             + mTask.getParent().getContentDescription(),
                     "clicking an overview task");
-            mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, TASK_START_EVENT);
-            return new LaunchedAppState(mLauncher);
+            if (mOverview.getContainerType()
+                    == LauncherInstrumentation.ContainerType.SPLIT_SCREEN_SELECT) {
+                mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SPLIT_START_EVENT);
+
+                try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                        "launched splitscreen")) {
+
+                    BySelector divider = By.res(SYSTEMUI_PACKAGE, "docked_divider_handle");
+                    mLauncher.waitForSystemUiObject(divider);
+                    return new LaunchedAppState(mLauncher);
+                }
+            } else {
+                mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, TASK_START_EVENT);
+                return new LaunchedAppState(mLauncher);
+            }
         }
     }
+
+    /** Taps the task menu. */
+    @NonNull
+    public OverviewTaskMenu tapMenu() {
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+             LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                     "want to tap the task menu")) {
+            mLauncher.clickLauncherObject(
+                    mLauncher.waitForObjectInContainer(mTask.getParent(), "icon"));
+
+            try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+                    "tapped the task menu")) {
+                return new OverviewTaskMenu(mLauncher);
+            }
+        }
+    }
+
+    boolean isTaskSplit() {
+        return mLauncher.findObjectInContainer(mTask.getParent(), "bottomright_snapshot") != null;
+    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java b/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java
new file mode 100644
index 0000000..8cdc8a0
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java
@@ -0,0 +1,52 @@
+/*
+ * 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.tapl;
+
+import androidx.annotation.NonNull;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiObject2;
+
+/** Represents the menu of an overview task. */
+public class OverviewTaskMenu {
+
+    private final LauncherInstrumentation mLauncher;
+    private final UiObject2 mMenu;
+
+    OverviewTaskMenu(LauncherInstrumentation launcher) {
+        mLauncher = launcher;
+
+        mMenu = mLauncher.waitForLauncherObject("menu_option_layout");
+        mLauncher.assertTrue("The overview task menus is not visible",
+                !mMenu.getVisibleBounds().isEmpty());
+    }
+
+    /** Taps the split menu item from the overview task menu. */
+    @NonNull
+    public SplitScreenSelect tapSplitMenuItem() {
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+             LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                     "tap split menu item")) {
+            mLauncher.clickLauncherObject(
+                    mLauncher.findObjectInContainer(mMenu, By.textStartsWith("Split")));
+
+            try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+                    "tapped split menu item")) {
+                return new SplitScreenSelect(mLauncher);
+            }
+        }
+    }
+}
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/SplitScreenSelect.java b/tests/tapl/com/android/launcher3/tapl/SplitScreenSelect.java
new file mode 100644
index 0000000..9fa74e4
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/SplitScreenSelect.java
@@ -0,0 +1,41 @@
+/*
+ * 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.tapl;
+
+import com.android.launcher3.tapl.LauncherInstrumentation.ContainerType;
+
+/**
+ * Represents a special state in Overview where the initial split app is shoved to the side and a
+ * second split app can be selected.
+ */
+public class SplitScreenSelect extends Overview {
+
+    SplitScreenSelect(LauncherInstrumentation launcher) {
+        super(launcher);
+    }
+
+    @Override
+    protected ContainerType getContainerType() {
+        return ContainerType.SPLIT_SCREEN_SELECT;
+    }
+
+    @Override
+    protected boolean isActionsViewVisible() {
+        // We don't show overview actions in split select state.
+        return false;
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/Taskbar.java b/tests/tapl/com/android/launcher3/tapl/Taskbar.java
index b5a08c3..0f9d5f5 100644
--- a/tests/tapl/com/android/launcher3/tapl/Taskbar.java
+++ b/tests/tapl/com/android/launcher3/tapl/Taskbar.java
@@ -15,8 +15,9 @@
  */
 package com.android.launcher3.tapl;
 
-import static com.android.launcher3.testing.TestProtocol.REQUEST_DISABLE_MANUAL_TASKBAR_STASHING;
-import static com.android.launcher3.testing.TestProtocol.REQUEST_ENABLE_MANUAL_TASKBAR_STASHING;
+import static com.android.launcher3.tapl.LauncherInstrumentation.TASKBAR_RES_ID;
+import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_DISABLE_MANUAL_TASKBAR_STASHING;
+import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_ENABLE_MANUAL_TASKBAR_STASHING;
 
 import android.graphics.Point;
 import android.os.SystemClock;
@@ -51,7 +52,7 @@
         try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                 "want to get a taskbar icon")) {
             return new TaskbarAppIcon(mLauncher, mLauncher.waitForObjectInContainer(
-                    mLauncher.waitForLauncherObject("taskbar_view"),
+                    mLauncher.waitForLauncherObject(TASKBAR_RES_ID),
                     AppIcon.getAppIconSelector(appName, mLauncher)));
         }
     }
@@ -67,7 +68,7 @@
         try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                 "want to hide the taskbar");
              LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
-            mLauncher.waitForLauncherObject("taskbar_view");
+            mLauncher.waitForLauncherObject(TASKBAR_RES_ID);
 
             final long downTime = SystemClock.uptimeMillis();
             Point stashTarget = new Point(
@@ -96,7 +97,7 @@
              LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
 
             mLauncher.clickLauncherObject(mLauncher.waitForObjectInContainer(
-                    mLauncher.waitForLauncherObject("taskbar_view"), getAllAppsButtonSelector()));
+                    mLauncher.waitForLauncherObject(TASKBAR_RES_ID), getAllAppsButtonSelector()));
 
             return new AllAppsFromTaskbar(mLauncher);
         }
@@ -107,7 +108,7 @@
         try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                 "want to get all taskbar icons")) {
             return mLauncher.waitForObjectsInContainer(
-                    mLauncher.waitForLauncherObject("taskbar_view"),
+                    mLauncher.waitForLauncherObject(TASKBAR_RES_ID),
                     AppIcon.getAnyAppIconSelector())
                     .stream()
                     .map(UiObject2::getText)
diff --git a/tests/tapl/com/android/launcher3/tapl/TaskbarAppIconMenuItem.java b/tests/tapl/com/android/launcher3/tapl/TaskbarAppIconMenuItem.java
index 69a8a08..424c58e 100644
--- a/tests/tapl/com/android/launcher3/tapl/TaskbarAppIconMenuItem.java
+++ b/tests/tapl/com/android/launcher3/tapl/TaskbarAppIconMenuItem.java
@@ -17,7 +17,7 @@
 
 import androidx.test.uiautomator.UiObject2;
 
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 
 import java.util.regex.Pattern;
 
diff --git a/tests/tapl/com/android/launcher3/tapl/Widget.java b/tests/tapl/com/android/launcher3/tapl/Widget.java
index 2346249..29eb46c 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widget.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widget.java
@@ -23,7 +23,7 @@
 import androidx.test.uiautomator.UiObject2;
 import androidx.test.uiautomator.Until;
 
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 
 import java.util.regex.Pattern;
 
@@ -69,7 +69,24 @@
      */
     @NonNull
     public WidgetResizeFrame dragWidgetToWorkspace() {
-        return dragWidgetToWorkspace(/* configurable= */ false, /* acceptsConfig= */ false);
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+            return dragWidgetToWorkspace(/* configurable= */ false, /* acceptsConfig= */ false, -1,
+                    -1);
+        }
+    }
+
+    /**
+     * Drags a non-configurable widget from the widgets container to the workspace at cellX and
+     * cellY and returns the resize frame that is shown after the widget is added.
+     */
+    @NonNull
+    public WidgetResizeFrame dragWidgetToWorkspace(int cellX, int cellY) {
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+             LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                     "Dragging widget to workspace cell " + cellX + "," + cellY)) {
+            return dragWidgetToWorkspace(/* configurable= */ false, /* acceptsConfig= */ false,
+                    cellX, cellY);
+        }
     }
 
     /**
@@ -79,7 +96,32 @@
      */
     @Nullable
     public WidgetResizeFrame dragConfigWidgetToWorkspace(boolean acceptsConfig) {
-        return dragWidgetToWorkspace(/* configurable= */ true, acceptsConfig);
+        // TODO(b/239438337, fransebas) add correct event checking for this case
+        //try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+        return dragWidgetToWorkspace(/* configurable= */ true, acceptsConfig, -1, -1);
+        //}
+    }
+
+    /**
+     * Drags an object to the center of homescreen.
+     *
+     * @param startsActivity   whether it's expected to start an activity.
+     * @param isWidgetShortcut whether we drag a widget shortcut
+     * @param cellX            X position in the CellLayout
+     * @param cellY            Y position in the CellLayout
+     */
+    private void dragToWorkspace(boolean startsActivity, boolean isWidgetShortcut, int cellX,
+            int cellY) {
+        Launchable launchable = getLaunchable();
+        LauncherInstrumentation launcher = launchable.mLauncher;
+        Workspace.dragIconToWorkspace(
+                launcher,
+                launchable,
+                () -> Workspace.getCellCenter(launchable.mLauncher, cellX, cellY),
+                startsActivity,
+                isWidgetShortcut,
+                launchable::addExpectedEventsForLongClick);
+
     }
 
     /**
@@ -88,11 +130,28 @@
      *
      * <p> If {@code configurable} is true, then either accepts or cancels the configuration based
      * on {@code acceptsConfig}.
+     * <p> If either {@code cellX} or {@code cellY} are negative, then a default location would be
+     * chosen
+     *
+     * @param configurable  if the widget has a configuration activity.
+     * @param acceptsConfig if the widget has a configuration, then if we should accept it or
+     *                      cancel it
+     * @param cellX         X position to drop the widget in the workspace
+     * @param cellY         Y position to drop the widget in the workspace
+     * @return returns the given resize frame of the widget after being dropped, if
+     * configurable is true and acceptsConfig is false then the widget would not be places and will
+     * be cancel and it returns null.
      */
     @Nullable
-    private WidgetResizeFrame dragWidgetToWorkspace(
-            boolean configurable, boolean acceptsConfig) {
-        dragToWorkspace(/* startsActivity= */ configurable, /* isWidgetShortcut= */ false);
+    private WidgetResizeFrame dragWidgetToWorkspace(boolean configurable, boolean acceptsConfig,
+            int cellX, int cellY) {
+        if (cellX == -1 || cellY == -1) {
+            internalDragToWorkspace(/* startsActivity= */ configurable, /* isWidgetShortcut= */
+                    false);
+        } else {
+            dragToWorkspace(/* startsActivity= */ configurable, /* isWidgetShortcut= */ false,
+                    cellX, cellY);
+        }
 
         if (configurable) {
             // Configure the widget.
diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java
index 7fd68c0..96e2e3f 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widgets.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java
@@ -27,7 +27,7 @@
 import androidx.test.uiautomator.UiObject2;
 import androidx.test.uiautomator.Until;
 
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.shared.TestProtocol;
 
 import java.util.Collection;
 
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index eb7f05b..2c9fdb3 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -18,8 +18,8 @@
 
 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_SCROLLED;
 
-import static com.android.launcher3.testing.TestProtocol.ALL_APPS_STATE_ORDINAL;
-import static com.android.launcher3.testing.TestProtocol.NORMAL_STATE_ORDINAL;
+import static com.android.launcher3.testing.shared.TestProtocol.ALL_APPS_STATE_ORDINAL;
+import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
 
 import static junit.framework.TestCase.assertNotNull;
 import static junit.framework.TestCase.assertTrue;
@@ -39,8 +39,9 @@
 import androidx.test.uiautomator.UiObject2;
 import androidx.test.uiautomator.Until;
 
-import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.testing.WorkspaceCellCenterRequest;
+import com.android.launcher3.testing.shared.HotseatCellCenterRequest;
+import com.android.launcher3.testing.shared.TestProtocol;
+import com.android.launcher3.testing.shared.WorkspaceCellCenterRequest;
 
 import java.util.List;
 import java.util.Map;
@@ -89,7 +90,7 @@
             final int windowCornerRadius = (int) Math.ceil(mLauncher.getWindowCornerRadius());
             final int startY = deviceHeight - Math.max(bottomGestureMargin, windowCornerRadius) - 1;
             final int swipeHeight = mLauncher.getTestInfo(
-                    TestProtocol.REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT)
+                            TestProtocol.REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT)
                     .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
             LauncherInstrumentation.log(
                     "switchToAllApps: deviceHeight = " + deviceHeight + ", startY = " + startY
@@ -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.
@@ -177,6 +192,12 @@
         }
     }
 
+    /** Returns the number of pages. */
+    public int getPageCount() {
+        final UiObject2 workspace = verifyActiveContainer();
+        return workspace.getChildCount();
+    }
+
     /**
      * Returns the number of pages that are visible on the screen simultaneously.
      */
@@ -214,10 +235,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,8 +258,26 @@
     }
 
     /**
+     * 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.
+     * lower x coordinate is selected.
      */
     public Map<String, Point> getWorkspaceIconsPositions() {
         final UiObject2 workspace = verifyActiveContainer();
@@ -250,6 +290,7 @@
                                 /* valueMapper= */ UiObject2::getVisibleCenter,
                                 /* mergeFunction= */ (p1, p2) -> p1.x < p2.x ? p1 : p2));
     }
+
     /*
      * Get the center point of the delete/uninstall icon in the drop target bar.
      */
@@ -345,6 +386,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 +418,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 +426,7 @@
                     LauncherInstrumentation.EVENT_START);
         }
         dragIconToWorkspace(
-                launcher, launchable, () -> dest, expectLongClickEvents, expectDropEvents);
+                launcher, launchable, dest, expectLongClickEvents, expectDropEvents);
     }
 
     /**
@@ -429,7 +475,9 @@
             // Since the destination can be on another page, we need to drag to the edge first
             // until we reach the target page
             while (targetDest.x > displayX || targetDest.x < 0) {
-                int edgeX = targetDest.x > 0 ? displayX : 0;
+                // Don't drag all the way to the edge to prevent touch events from getting out of
+                //screen bounds.
+                int edgeX = targetDest.x > 0 ? displayX - 1 : 1;
                 Point screenEdge = new Point(edgeX, targetDest.y);
                 Point finalDragStart = dragStart;
                 executeAndWaitForPageScroll(launcher,
@@ -456,6 +504,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.
@@ -523,6 +590,32 @@
         }
     }
 
+    /**
+     * @param cellX X position of the widget trying to get.
+     * @param cellY Y position of the widget trying to get.
+     * @return returns the Widget in the given position in the Launcher or an Exception if no such
+     * widget is in that position.
+     */
+    @NonNull
+    public Widget getWidgetAtCell(int cellX, int cellY) {
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "getting widget at cell position " + cellX + "," + cellY)) {
+            final List<UiObject2> widgets = mLauncher.waitForObjectsBySelector(
+                    By.clazz("com.android.launcher3.widget.LauncherAppWidgetHostView"));
+            Point coordinateInScreen = Workspace.getCellCenter(mLauncher, cellX, cellY);
+            for (UiObject2 widget : widgets) {
+                if (widget.getVisibleBounds().contains(coordinateInScreen.x,
+                        coordinateInScreen.y)) {
+                    return new Widget(mLauncher, widget);
+                }
+            }
+        }
+        mLauncher.fail("Unable to find widget at cell " + cellX + "," + cellY);
+        // This statement is unreachable because mLauncher.fail throws an exception
+        // but is needed for compiling
+        return null;
+    }
+
     @Nullable
     public Widget tryGetPendingWidget(long timeout) {
         try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
diff --git a/tests/tapl/com/android/launcher3/tapl/WorkspaceDragSource.java b/tests/tapl/com/android/launcher3/tapl/WorkspaceDragSource.java
index d8d4420..141476c 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 {
 
@@ -30,20 +32,67 @@
         Launchable launchable = getLaunchable();
         LauncherInstrumentation launcher = launchable.mLauncher;
         try (LauncherInstrumentation.Closable e = launcher.eventsCheck()) {
-            final Point launchableCenter = launchable.getObject().getVisibleCenter();
-            final Point displaySize = launcher.getRealDisplaySize();
-            final int width = displaySize.x / 2;
+            internalDragToWorkspace(startsActivity, isWidgetShortcut);
+        }
+    }
+
+    /**
+     * TODO(Redesign WorkspaceDragSource to have actual private methods)
+     * Temporary private method
+     *
+     * @param startsActivity   whether it's expected to start an activity.
+     * @param isWidgetShortcut whether we drag a widget shortcut
+     */
+    default void internalDragToWorkspace(boolean startsActivity, boolean isWidgetShortcut) {
+        Launchable launchable = getLaunchable();
+        LauncherInstrumentation launcher = launchable.mLauncher;
+        final Point launchableCenter = launchable.getObject().getVisibleCenter();
+        final Point displaySize = launcher.getRealDisplaySize();
+        final int width = displaySize.x / 2;
+        Workspace.dragIconToWorkspace(
+                launcher,
+                launchable,
+                () -> new Point(
+                        launchableCenter.x >= width
+                                ? launchableCenter.x - width / 2
+                                : launchableCenter.x + width / 2,
+                        displaySize.y / 2),
+                startsActivity,
+                isWidgetShortcut,
+                launchable::addExpectedEventsForLongClick);
+    }
+
+    /**
+     * 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,
-                    new Point(
-                            launchableCenter.x >= width
-                                    ? launchableCenter.x - width / 2
-                                    : launchableCenter.x + width / 2,
-                            displaySize.y / 2),
-                    startsActivity,
-                    isWidgetShortcut,
-                    launchable::addExpectedEventsForLongClick);
+                    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;
+            }
         }
     }