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

am skip reason: subject contains skip directive

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

Change-Id: I4e06e170acfc3f55d227dbd6257f21d09e3ad502
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/Android.bp b/Android.bp
index 0a55675..4adbf53 100644
--- a/Android.bp
+++ b/Android.bp
@@ -313,3 +313,138 @@
         baseline_filename: "lint-baseline-launcher3.xml",
     },
 }
+
+// Build rule for Launcher3 Go app for Android Go devices.
+android_app {
+    name: "Launcher3Go",
+
+    static_libs: ["Launcher3CommonDepsLib"],
+
+    srcs: [
+        "src/**/*.java",
+        "src_ui_overrides/**/*.java",
+        "go/src/**/*.java",
+    ],
+
+    resource_dirs: ["go/res"],
+
+    optimize: {
+        proguard_flags_files: ["proguard.flags"],
+    },
+
+    sdk_version: "current",
+    min_sdk_version: "current",
+    target_sdk_version: "current",
+    privileged: true,
+    system_ext_specific: true,
+    overrides: [
+        "Home",
+        "Launcher2",
+        "Launcher3",
+        "Launcher3QuickStep",
+    ],
+    required: ["privapp_whitelist_com.android.launcher3"],
+
+    additional_manifests: [
+        "AndroidManifest.xml",
+        "AndroidManifest-common.xml",
+    ],
+
+    manifest: "go/AndroidManifest.xml",
+    jacoco: {
+        include_filter: ["com.android.launcher3.*"],
+    }
+
+}
+
+// Build rule for Quickstep app.
+android_app {
+    name: "Launcher3QuickStep",
+
+    static_libs: ["Launcher3QuickStepLib"],
+    optimize: {
+        enabled: false,
+    },
+
+    platform_apis: true,
+    min_sdk_version: "current",
+    target_sdk_version: "current",
+
+    privileged: true,
+    system_ext_specific: true,
+    overrides: [
+        "Home",
+        "Launcher2",
+        "Launcher3",
+    ],
+    required: ["privapp_whitelist_com.android.launcher3"],
+
+    resource_dirs: ["quickstep/res"],
+
+    additional_manifests: [
+        "quickstep/AndroidManifest-launcher.xml",
+        "AndroidManifest-common.xml",
+    ],
+
+    manifest: "quickstep/AndroidManifest.xml",
+    jacoco: {
+        include_filter: ["com.android.launcher3.*"],
+    }
+
+}
+
+// Build rule for Launcher3 Go app with quickstep for Android Go devices.
+android_app {
+    name: "Launcher3QuickStepGo",
+
+    static_libs: [
+        "SystemUI-statsd",
+        "SystemUISharedLib",
+        "LauncherGoResLib",
+    ],
+
+    platform_apis: true,
+    min_sdk_version: "current",
+    target_sdk_version: "current",
+
+    srcs: [
+        "src/**/*.java",
+        "quickstep/src/**/*.java",
+        "go/src/**/*.java",
+        "go/quickstep/src/**/*.java",
+    ],
+
+    resource_dirs: [
+        "go/quickstep/res",
+        "go/res",
+        "quickstep/res",
+    ],
+
+    optimize: {
+        proguard_flags_files: ["proguard.flags"],
+        enabled: true,
+    },
+
+    privileged: true,
+    system_ext_specific: true,
+    overrides: [
+        "Home",
+        "Launcher2",
+        "Launcher3",
+        "Launcher3QuickStep",
+    ],
+    required: ["privapp_whitelist_com.android.launcher3"],
+
+    additional_manifests: [
+        "go/AndroidManifest.xml",
+        "go/AndroidManifest-launcher.xml",
+        "AndroidManifest-common.xml",
+    ],
+
+    manifest: "quickstep/AndroidManifest.xml",
+    jacoco: {
+        include_filter: ["com.android.launcher3.*"],
+    }
+
+}
+
diff --git a/Android.mk b/Android.mk
deleted file mode 100644
index 1bc8b28..0000000
--- a/Android.mk
+++ /dev/null
@@ -1,147 +0,0 @@
-#
-# Copyright (C) 2013 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-#
-# Build rule for Launcher3 Go app for Android Go devices.
-#
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_MODULE_TAGS := optional
-LOCAL_STATIC_ANDROID_LIBRARIES := Launcher3CommonDepsLib
-
-LOCAL_SRC_FILES := \
-    $(call all-java-files-under, src) \
-    $(call all-java-files-under, src_ui_overrides) \
-    $(call all-java-files-under, go/src)
-
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/go/res
-
-LOCAL_PROGUARD_FLAG_FILES := proguard.flags
-
-LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 26
-LOCAL_PACKAGE_NAME := Launcher3Go
-LOCAL_PRIVILEGED_MODULE := true
-LOCAL_SYSTEM_EXT_MODULE := true
-LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 Launcher3QuickStep
-LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3
-
-LOCAL_FULL_LIBS_MANIFEST_FILES := \
-    $(LOCAL_PATH)/AndroidManifest.xml \
-    $(LOCAL_PATH)/AndroidManifest-common.xml
-
-LOCAL_MANIFEST_FILE := go/AndroidManifest.xml
-LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.launcher3.*
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_LICENSE_PACKAGE_NAME := Android Launcher3
-LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
-include $(BUILD_PACKAGE)
-
-#
-# Build rule for Quickstep app.
-#
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_STATIC_ANDROID_LIBRARIES := Launcher3QuickStepLib
-LOCAL_PROGUARD_ENABLED := disabled
-
-ifneq (,$(wildcard frameworks/base))
-  LOCAL_PRIVATE_PLATFORM_APIS := true
-else
-  LOCAL_SDK_VERSION := system_current
-  LOCAL_MIN_SDK_VERSION := 26
-endif
-LOCAL_PACKAGE_NAME := Launcher3QuickStep
-LOCAL_PRIVILEGED_MODULE := true
-LOCAL_SYSTEM_EXT_MODULE := true
-LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3
-LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3
-
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/quickstep/res
-
-LOCAL_FULL_LIBS_MANIFEST_FILES := \
-    $(LOCAL_PATH)/quickstep/AndroidManifest-launcher.xml \
-    $(LOCAL_PATH)/AndroidManifest-common.xml
-
-LOCAL_MANIFEST_FILE := quickstep/AndroidManifest.xml
-LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.launcher3.*
-
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_LICENSE_PACKAGE_NAME := Android Launcher3
-LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
-include $(BUILD_PACKAGE)
-
-
-#
-# Build rule for Launcher3 Go app with quickstep for Android Go devices.
-#
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    SystemUI-statsd \
-    SystemUISharedLib
-ifneq (,$(wildcard frameworks/base))
-  LOCAL_PRIVATE_PLATFORM_APIS := true
-else
-  LOCAL_SDK_VERSION := system_current
-  LOCAL_MIN_SDK_VERSION := 26
-endif
-LOCAL_STATIC_ANDROID_LIBRARIES := LauncherGoResLib
-
-LOCAL_SRC_FILES := \
-    $(call all-java-files-under, src) \
-    $(call all-java-files-under, quickstep/src) \
-    $(call all-java-files-under, go/src) \
-    $(call all-java-files-under, go/quickstep/src)
-
-LOCAL_RESOURCE_DIR := \
-    $(LOCAL_PATH)/go/quickstep/res \
-    $(LOCAL_PATH)/go/res \
-    $(LOCAL_PATH)/quickstep/res
-
-LOCAL_PROGUARD_FLAG_FILES := proguard.flags
-LOCAL_PROGUARD_ENABLED := full
-
-LOCAL_PACKAGE_NAME := Launcher3QuickStepGo
-LOCAL_PRIVILEGED_MODULE := true
-LOCAL_SYSTEM_EXT_MODULE := true
-LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 Launcher3QuickStep
-LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3
-
-LOCAL_FULL_LIBS_MANIFEST_FILES := \
-    $(LOCAL_PATH)/go/AndroidManifest.xml \
-    $(LOCAL_PATH)/go/AndroidManifest-launcher.xml \
-    $(LOCAL_PATH)/AndroidManifest-common.xml
-
-LOCAL_MANIFEST_FILE := quickstep/AndroidManifest.xml
-LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.launcher3.*
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_LICENSE_PACKAGE_NAME := Android Launcher3
-LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
-include $(BUILD_PACKAGE)
-
-
-# ==================================================
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/OWNERS b/OWNERS
index 7f98ea6..560b562 100644
--- a/OWNERS
+++ b/OWNERS
@@ -7,13 +7,6 @@
 alexchau@google.com
 andraskloczl@google.com
 patmanning@google.com
-petrcermak@google.com
-pbdr@google.com
-kideckel@google.com
-stevenckng@google.com
-ydixit@google.com
-boadway@google.com
-alinazaidi@google.com
 adamcohen@google.com
 hyunyoungs@google.com
 mrcasey@google.com
@@ -24,10 +17,8 @@
 zakcohen@google.com
 santie@google.com
 vadimt@google.com
-mett@google.com
 jonmiranda@google.com
 pinyaoting@google.com
-sfufa@google.com
 gwasserman@google.com
 jamesoleary@google.com
 joshtrask@google.com
@@ -37,8 +28,6 @@
 tracyzhou@google.com
 peanutbutter@google.com
 xuqiu@google.com
-sreyasr@google.com
-thiruram@google.com
 brianji@google.com
 
 per-file FeatureFlags.java, globs = set noparent
diff --git a/go/quickstep/res/values-ro/strings.xml b/go/quickstep/res/values-ro/strings.xml
index 3d6f0d8..0be8cce 100644
--- a/go/quickstep/res/values-ro/strings.xml
+++ b/go/quickstep/res/values-ro/strings.xml
@@ -1,20 +1,20 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_share_drop_target_label" msgid="5804774105974539508">"Trimite aplicația"</string>
-    <string name="action_listen" msgid="2370304050784689486">"Ascultă"</string>
-    <string name="action_translate" msgid="8028378961867277746">"Tradu"</string>
+    <string name="app_share_drop_target_label" msgid="5804774105974539508">"Trimiteți aplicația"</string>
+    <string name="action_listen" msgid="2370304050784689486">"Ascultați"</string>
+    <string name="action_translate" msgid="8028378961867277746">"Traduceți"</string>
     <string name="action_search" msgid="6269564710943755464">"Lens"</string>
     <string name="dialog_acknowledge" msgid="2804025517675853172">"OK"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"ANULEAZĂ"</string>
+    <string name="dialog_cancel" msgid="6464336969134856366">"ANULAȚI"</string>
     <string name="dialog_settings" msgid="6564397136021186148">"SETĂRI"</string>
-    <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Tradu sau ascultă textul de pe ecran"</string>
-    <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Informații precum textul de pe ecran, adresele web și capturile de ecran pot fi trimise la Google.\n\nCa să schimbi informațiile trimise, accesează "<b>"Setări &gt; Aplicații &gt; Aplicații prestabilite &gt; Aplicația asistent digital"</b>"."</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"Alege un asistent pentru a folosi această funcție"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"Pentru a asculta sau a traduce text de pe ecran, alege o aplicație asistent digital în Setări"</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"Schimbă asistentul pentru a folosi această funcție"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"Pentru a asculta sau a traduce text de pe ecran, schimbă aplicația asistent digital în Setări"</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"Atinge aici pentru a asculta text de pe ecran"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"Atinge aici pentru a traduce text de pe ecran"</string>
+    <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Traduceți sau ascultați textul de pe ecran"</string>
+    <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Informații precum textul de pe ecran, adresele web și capturile de ecran pot fi trimise la Google.\n\nCa să schimbați informațiile trimise, accesați "<b>"Setări &gt; Aplicații &gt; Aplicații prestabilite &gt; Aplicația asistent digital"</b>"."</string>
+    <string name="assistant_not_selected_title" msgid="5017072974603345228">"Alegeți un asistent pentru a folosi această funcție"</string>
+    <string name="assistant_not_selected_text" msgid="3244613673884359276">"Pentru a asculta sau a traduce text de pe ecran, alegeți o aplicație asistent digital în Setări"</string>
+    <string name="assistant_not_supported_title" msgid="1675788067597484142">"Schimbați asistentul pentru a folosi această funcție"</string>
+    <string name="assistant_not_supported_text" msgid="1708031078549268884">"Pentru a asculta sau a traduce text de pe ecran, schimbați aplicația asistent digital în Setări"</string>
+    <string name="tooltip_listen" msgid="7634466447860989102">"Atingeți aici pentru a asculta text de pe ecran"</string>
+    <string name="tooltip_translate" msgid="4184845868901542567">"Atingeți aici pentru a traduce text de pe ecran"</string>
     <string name="toast_p2p_app_not_shareable" msgid="7229739094132131536">"Aplicația nu poate fi distribuită"</string>
 </resources>
diff --git a/protos/view_capture.proto b/protos/view_capture.proto
new file mode 100644
index 0000000..98574dd
--- /dev/null
+++ b/protos/view_capture.proto
@@ -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.
+ */
+
+syntax = "proto2";
+
+package com.android.launcher3.view;
+
+option java_outer_classname = "ViewCaptureData";
+
+message ExportedData {
+
+  repeated FrameData frameData = 1;
+}
+
+message FrameData {
+  optional int64 timestamp = 1;
+  optional ViewNode node = 2;
+}
+
+message ViewNode {
+  optional string classname = 1;
+  optional string id = 2;
+  optional int32 left = 3;
+  optional int32 top = 4;
+  optional int32 width = 5;
+  optional int32 height = 6;
+  optional int32 scrollX = 7;
+  optional int32 scrollY = 8;
+
+  optional float translationX = 9;
+  optional float translationY = 10;
+  optional float scaleX = 11 [default = 1];
+  optional float scaleY = 12 [default = 1];
+  optional float alpha = 13 [default = 1];
+
+  optional bool willNotDraw = 14;
+  optional bool clipChildren = 15;
+  optional int32 visibility = 16;
+
+  repeated ViewNode children = 17;
+}
diff --git a/quickstep/Android.bp b/quickstep/Android.bp
index 70b1438..f739f81 100644
--- a/quickstep/Android.bp
+++ b/quickstep/Android.bp
@@ -26,7 +26,7 @@
 filegroup {
     name: "launcher3-quickstep-tests-src",
     path: "tests",
-    srcs: ["tests/src/**/*.java"],
+    srcs: ["tests/src/**/*.java", "tests/src/**/*.kt"],
 }
 
 filegroup {
diff --git a/quickstep/res/drawable/split_instructions_background.xml b/quickstep/res/drawable/split_instructions_background.xml
new file mode 100644
index 0000000..6d0e7db
--- /dev/null
+++ b/quickstep/res/drawable/split_instructions_background.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:shape="rectangle">
+    <solid android:color="?androidprv:attr/colorAccentPrimary" />
+    <corners android:radius="@dimen/split_instructions_radius" />
+</shape>
\ No newline at end of file
diff --git a/quickstep/res/layout/split_instructions_view.xml b/quickstep/res/layout/split_instructions_view.xml
new file mode 100644
index 0000000..91fb05c
--- /dev/null
+++ b/quickstep/res/layout/split_instructions_view.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<com.android.quickstep.views.SplitInstructionsView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:background="@drawable/split_instructions_background"
+    android:paddingRight="@dimen/split_instructions_horizontal_padding"
+    android:paddingLeft="@dimen/split_instructions_horizontal_padding"
+    android:paddingTop="@dimen/split_instructions_vertical_padding"
+    android:paddingBottom="@dimen/split_instructions_vertical_padding"
+    android:elevation="@dimen/split_instructions_elevation"
+    android:visibility="gone">
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/split_instructions_text"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:gravity="center"
+        android:textColor="?androidprv:attr/textColorOnAccent"
+        android:text="@string/toast_split_select_app" />
+</com.android.quickstep.views.SplitInstructionsView>
\ No newline at end of file
diff --git a/quickstep/res/values-land/dimens.xml b/quickstep/res/values-land/dimens.xml
index f233bde..732f511 100644
--- a/quickstep/res/values-land/dimens.xml
+++ b/quickstep/res/values-land/dimens.xml
@@ -15,7 +15,8 @@
      limitations under the License.
 -->
 <resources>
-    <dimen name="overview_task_margin">8dp</dimen>
+    <!--  Overview actions  -->
+    <dimen name="overview_actions_top_margin">12dp</dimen>
 
     <!-- Tips Gesture Tutorial -->
     <dimen name="gesture_tutorial_feedback_margin_start_end">126dp</dimen>
diff --git a/quickstep/res/values-ro/strings.xml b/quickstep/res/values-ro/strings.xml
index ba4badf..fb661f2 100644
--- a/quickstep/res/values-ro/strings.xml
+++ b/quickstep/res/values-ro/strings.xml
@@ -19,11 +19,11 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="recent_task_option_pin" msgid="7929860679018978258">"Fixează"</string>
+    <string name="recent_task_option_pin" msgid="7929860679018978258">"Fixați"</string>
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Formă liberă"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Niciun element recent"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Setări de utilizare a aplicației"</string>
-    <string name="recents_clear_all" msgid="5328176793634888831">"Șterge tot"</string>
+    <string name="recents_clear_all" msgid="5328176793634888831">"Ștergeți tot"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Aplicații recente"</string>
     <string name="task_view_closed" msgid="9170038230110856166">"Activitatea s-a încheiat"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
@@ -31,75 +31,75 @@
     <string name="time_left_for_app" msgid="3111996412933644358">"Au mai rămas <xliff:g id="TIME">%1$s</xliff:g> astăzi"</string>
     <string name="title_app_suggestions" msgid="4185902664111965088">"Sugestii de aplicații"</string>
     <string name="all_apps_prediction_tip" msgid="2672336544844936186">"Aplicațiile estimate"</string>
-    <string name="hotseat_edu_title_migrate" msgid="306578144424489980">"Primește sugestii de aplicații în rândul de jos al ecranului de pornire"</string>
-    <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_title_migrate" msgid="306578144424489980">"Primiți sugestii de aplicații în rândul de jos al ecranului de pornire"</string>
+    <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Primiți sugestii de aplicații în rândul de preferințe al ecranului de pornire"</string>
+    <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Accesați 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">"Accesați cu ușurință cele mai folosite aplicații direct din ecranul de pornire. Sugestiile se vor schimba în funcție de rutina dvs. 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">"Accesați 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">"Primiți 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>
     <string name="hotseat_auto_enrolled" msgid="522100018967146807">"Cele mai folosite aplicații apar aici și se modifică în funcție de rutine"</string>
-    <string name="hotseat_tip_no_empty_slots" msgid="1325212677738179185">"Trage aplicații din rândul de jos pentru a primi sugestii de aplicații"</string>
+    <string name="hotseat_tip_no_empty_slots" msgid="1325212677738179185">"Trageți aplicații din rândul de jos pentru a primi sugestii de aplicații"</string>
     <string name="hotseat_tip_gaps_filled" msgid="3035673010274223538">"Sugestiile de aplicații sunt adăugate în spațiile goale"</string>
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Sugestiile de aplicații au fost activate"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Sugestiile de aplicații au fost dezactivate"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Aplicația estimată: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
-    <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="1711645592102201538">"Glisează dinspre marginea dreaptă îndepărtată sau dinspre marginea stângă îndepărtată."</string>
-    <string name="back_gesture_feedback_cancelled" msgid="3274382913290074496">"Glisează dinspre marginea dreaptă sau stângă spre mijlocul ecranului și eliberează."</string>
-    <string name="back_gesture_feedback_complete_with_overview_follow_up" msgid="9176400654037014471">"Ai învățat cum să glisezi din dreapta pentru a reveni. Acum află cum să comuți aplicațiile."</string>
-    <string name="back_gesture_feedback_complete_without_follow_up" msgid="6405649621667113830">"Ai finalizat gestul „înapoi”."</string>
-    <string name="back_gesture_feedback_swipe_in_nav_bar" msgid="1148198467090405643">"Nu glisa prea aproape de partea de jos a ecranului."</string>
-    <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Schimbă sensibilitatea gestului „Înapoi” accesând Setările"</string>
-    <string name="back_gesture_intro_title" msgid="19551256430224428">"Glisează pentru a reveni"</string>
-    <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Pentru a reveni la ultimul ecran, glisează de la marginea stângă sau dreaptă spre mijlocul ecranului."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Pentru a reveni la ultimul ecran, glisează cu două degete dinspre marginea stângă sau dreaptă spre mijlocul ecranului."</string>
-    <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="1446774096007065298">"Glisează în sus dinspre marginea de jos a ecranului."</string>
-    <string name="home_gesture_feedback_overview_detected" msgid="1557523944897393013">"Nu întrerupe gestul înainte de a elibera."</string>
-    <string name="home_gesture_feedback_wrong_swipe_direction" msgid="6993979358080825438">"Glisează direct în sus."</string>
-    <string name="home_gesture_feedback_complete_with_follow_up" msgid="1427872029729605034">"Ai finalizat gestul „accesează ecranul de pornire”. Acum află cum să revii."</string>
-    <string name="home_gesture_feedback_complete_without_follow_up" msgid="8049099486868933882">"Ai finalizat gestul „accesează ecranul de pornire”."</string>
-    <string name="home_gesture_intro_title" msgid="836590312858441830">"Glisează pentru a accesa ecranul de pornire"</string>
-    <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Glisează în sus din partea de jos a ecranului. Cu acest gest accesezi mereu ecranul de pornire."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Glisează în sus cu două degete din partea de jos. Cu acest gest accesezi mereu ecranul de pornire."</string>
-    <string name="overview_gesture_feedback_swipe_too_far_from_edge" msgid="3032757898111577225">"Glisează în sus dinspre marginea de jos a ecranului."</string>
-    <string name="overview_gesture_feedback_home_detected" msgid="1411130969354020489">"Încearcă să ții fereastra mai mult înainte s-o eliberezi."</string>
-    <string name="overview_gesture_feedback_wrong_swipe_direction" msgid="6725820500906747925">"Glisează direct în sus, apoi întrerupe."</string>
-    <string name="overview_gesture_feedback_complete_with_follow_up" msgid="3544611727467765026">"Ai învățat să folosești gesturi. Pentru a dezactiva gesturile, accesează Setările."</string>
-    <string name="overview_gesture_feedback_complete_without_follow_up" msgid="3199486203448379152">"Ai finalizat gestul „comută între aplicații”."</string>
-    <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Glisează pentru a comuta între aplicații"</string>
-    <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Ca să comuți între aplicații, glisează în sus din partea de jos a ecranului, așteaptă și eliberează."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Ca să comuți între aplicații, glisează cu două degete de jos în sus, așteaptă și eliberează"</string>
+    <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="1711645592102201538">"Glisați dinspre marginea dreaptă îndepărtată sau dinspre marginea stângă îndepărtată."</string>
+    <string name="back_gesture_feedback_cancelled" msgid="3274382913290074496">"Glisați dinspre marginea dreaptă sau stângă spre mijlocul ecranului și eliberați."</string>
+    <string name="back_gesture_feedback_complete_with_overview_follow_up" msgid="9176400654037014471">"Ați învățat cum să glisați dinspre dreapta pentru a reveni. În continuare, aflați cum să comutați aplicațiile."</string>
+    <string name="back_gesture_feedback_complete_without_follow_up" msgid="6405649621667113830">"Ați finalizat gestul „înapoi”."</string>
+    <string name="back_gesture_feedback_swipe_in_nav_bar" msgid="1148198467090405643">"Nu glisați prea aproape de partea de jos a ecranului."</string>
+    <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Schimbați sensibilitatea gestului „Înapoi” accesând Setările"</string>
+    <string name="back_gesture_intro_title" msgid="19551256430224428">"Glisați pentru a reveni"</string>
+    <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Pentru a reveni la ultimul ecran, glisați de la marginea stângă sau dreaptă spre mijlocul ecranului."</string>
+    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Pentru a reveni la ultimul ecran, glisați cu două degete dinspre marginea stângă sau dreaptă spre mijlocul ecranului."</string>
+    <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="1446774096007065298">"Glisați în sus dinspre marginea de jos a ecranului."</string>
+    <string name="home_gesture_feedback_overview_detected" msgid="1557523944897393013">"Nu întrerupeți gestul înainte de a elibera."</string>
+    <string name="home_gesture_feedback_wrong_swipe_direction" msgid="6993979358080825438">"Glisați direct în sus."</string>
+    <string name="home_gesture_feedback_complete_with_follow_up" msgid="1427872029729605034">"Ați finalizat gestul „accesați ecranul de pornire”. În continuare, aflați cum să reveniți."</string>
+    <string name="home_gesture_feedback_complete_without_follow_up" msgid="8049099486868933882">"Ați finalizat gestul „accesați ecranul de pornire”."</string>
+    <string name="home_gesture_intro_title" msgid="836590312858441830">"Glisați pentru a accesa ecranul de pornire"</string>
+    <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Glisați în sus din partea de jos a ecranului. Cu acest gest accesați întotdeauna ecranul de pornire."</string>
+    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Glisați în sus cu 2 degete din partea de jos. Cu acest gest accesați întotdeauna ecranul de pornire."</string>
+    <string name="overview_gesture_feedback_swipe_too_far_from_edge" msgid="3032757898111577225">"Glisați în sus dinspre marginea de jos a ecranului."</string>
+    <string name="overview_gesture_feedback_home_detected" msgid="1411130969354020489">"Încercați să țineți fereastra mai mult înainte s-o eliberați."</string>
+    <string name="overview_gesture_feedback_wrong_swipe_direction" msgid="6725820500906747925">"Glisați direct în sus, apoi întrerupeți."</string>
+    <string name="overview_gesture_feedback_complete_with_follow_up" msgid="3544611727467765026">"Ați învățat cum să folosiți gesturi. Pentru a dezactiva gesturile, accesați Setările."</string>
+    <string name="overview_gesture_feedback_complete_without_follow_up" msgid="3199486203448379152">"Ați finalizat gestul „comutați între aplicații”."</string>
+    <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Glisați pentru a comuta între aplicații"</string>
+    <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Ca să comutați între aplicații, glisați în sus din partea de jos a ecranului, așteptați și eliberați."</string>
+    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Ca să comutați între aplicații, glisați în sus cu 2 degete din partea de jos, așteptați și eliberați"</string>
     <string name="gesture_tutorial_confirm_title" msgid="6201516182040074092">"Gata"</string>
     <string name="gesture_tutorial_action_button_label" msgid="6249846312991332122">"Gata"</string>
     <string name="gesture_tutorial_action_button_label_settings" msgid="2923621047916486604">"Setări"</string>
-    <string name="gesture_tutorial_try_again" msgid="65962545858556697">"Reîncearcă"</string>
+    <string name="gesture_tutorial_try_again" msgid="65962545858556697">"Reîncercați"</string>
     <string name="gesture_tutorial_nice" msgid="2936275692616928280">"Bravo!"</string>
     <string name="gesture_tutorial_step" msgid="1279786122817620968">"Tutorialul <xliff:g id="CURRENT">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
     <string name="allset_title" msgid="5021126669778966707">"Gata!"</string>
-    <string name="allset_hint" msgid="2384632994739392447">"Glisează în sus pentru a accesa ecranul de pornire"</string>
-    <string name="allset_description" msgid="6350320429953234580">"Ești gata să folosești telefonul"</string>
-    <string name="allset_description_tablet" msgid="7332070270570039247">"Ești gata să folosești tableta"</string>
+    <string name="allset_hint" msgid="2384632994739392447">"Glisați în sus pentru a accesa ecranul de pornire"</string>
+    <string name="allset_description" msgid="6350320429953234580">"Sunteți gata să folosiți telefonul"</string>
+    <string name="allset_description_tablet" msgid="7332070270570039247">"Sunteți gata să folosiți tableta"</string>
     <string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Setările de navigare ale sistemului"</annotation></string>
-    <string name="action_share" msgid="2648470652637092375">"Distribuie"</string>
+    <string name="action_share" msgid="2648470652637092375">"Distribuiți"</string>
     <string name="action_screenshot" msgid="8171125848358142917">"Captură de ecran"</string>
     <string name="action_split" msgid="2098009717623550676">"Împărțit"</string>
-    <string name="toast_split_select_app" msgid="5453865907322018352">"Atinge altă aplicație pentru ecranul împărțit"</string>
+    <string name="toast_split_select_app" msgid="5453865907322018352">"Atingeți altă aplicație pentru a folosi ecranul împărțit"</string>
     <string name="toast_split_app_unsupported" msgid="3271526028981899666">"Aplicația nu acceptă ecranul împărțit."</string>
-    <string name="blocked_by_policy" msgid="2071401072261365546">"Această acțiune nu este permisă de aplicație sau de organizația ta"</string>
-    <string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Omiți tutorialul de navigare?"</string>
-    <string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Îl poți găsi mai târziu în aplicația <xliff:g id="NAME">%1$s</xliff:g>"</string>
-    <string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Anulează"</string>
-    <string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Omite"</string>
-    <string name="accessibility_rotate_button" msgid="4771825231336502943">"Rotește ecranul"</string>
+    <string name="blocked_by_policy" msgid="2071401072261365546">"Această acțiune nu este permisă de aplicație sau de organizația dvs."</string>
+    <string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Omiteți tutorialul de navigare?"</string>
+    <string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Îl puteți găsi mai târziu în aplicația <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Anulați"</string>
+    <string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Omiteți"</string>
+    <string name="accessibility_rotate_button" msgid="4771825231336502943">"Rotiți ecranul"</string>
     <string name="taskbar_edu_opened" msgid="3950252793551919129">"Panoul cu informații despre bara de activități s-a afișat"</string>
     <string name="taskbar_edu_closed" msgid="126643734478892862">"Panoul cu informații despre bara de activități s-a închis"</string>
-    <string name="taskbar_edu_switch_apps" msgid="6942863327845784813">"Folosește bara de activități ca să comuți între aplicații"</string>
-    <string name="taskbar_edu_splitscreen" msgid="2663361731630346489">"Trage în lateral ca să folosești două aplicații deodată"</string>
-    <string name="taskbar_edu_stashing" msgid="5212374387411764031">"Atinge lung oricând pentru a ascunde bara de activități"</string>
+    <string name="taskbar_edu_switch_apps" msgid="6942863327845784813">"Folosiți bara de activități ca să comutați între aplicații"</string>
+    <string name="taskbar_edu_splitscreen" msgid="2663361731630346489">"Trageți în lateral ca să folosiți două aplicații deodată"</string>
+    <string name="taskbar_edu_stashing" msgid="5212374387411764031">"Atingeți lung oricând pentru a ascunde bara de activități"</string>
     <string name="taskbar_edu_next" msgid="4007618274426775841">"Înainte"</string>
     <string name="taskbar_edu_previous" msgid="459202320127201702">"Înapoi"</string>
-    <string name="taskbar_edu_close" msgid="887022990168191073">"Închide"</string>
+    <string name="taskbar_edu_close" msgid="887022990168191073">"Închideți"</string>
     <string name="taskbar_edu_done" msgid="6880178093977704569">"Gata"</string>
     <string name="taskbar_button_home" msgid="2151398979630664652">"Ecran de pornire"</string>
     <string name="taskbar_button_a11y" msgid="5241161324875094465">"Accesibilitate"</string>
@@ -108,6 +108,6 @@
     <string name="taskbar_button_recents" msgid="7273376136216613134">"Recente"</string>
     <string name="taskbar_button_notifications" msgid="7471740351507357318">"Notificări"</string>
     <string name="taskbar_button_quick_settings" msgid="227662894293189391">"Setări rapide"</string>
-    <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Mută în stânga sus"</string>
-    <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Mută în dreapta jos"</string>
+    <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Mutați în stânga sus"</string>
+    <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Mutați în dreapta jos"</string>
 </resources>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 3072a3e..fa4eaed 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -36,6 +36,7 @@
     <dimen name="task_thumbnail_icon_drawable_size">44dp</dimen>
     <dimen name="overview_task_margin">16dp</dimen>
     <dimen name="overview_page_spacing">16dp</dimen>
+    <dimen name="task_icon_cache_default_icon_size">72dp</dimen>
 
     <item name="overview_max_scale" format="float" type="dimen">0.7</item>
     <item name="overview_modal_max_scale" format="float" type="dimen">1.1</item>
@@ -66,7 +67,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 +239,7 @@
     <dimen name="navigation_key_padding">0dp</dimen>
 
     <!-- Floating rotation button -->
-    <dimen name="floating_rotation_button_diameter">40dp</dimen>
+    <dimen name="floating_rotation_button_diameter">52dp</dimen>
     <dimen name="floating_rotation_button_min_margin">20dp</dimen>
     <dimen name="floating_rotation_button_taskbar_left_margin">20dp</dimen>
     <dimen name="floating_rotation_button_taskbar_bottom_margin">10dp</dimen>
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 2239102..ec497ef 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -20,6 +20,8 @@
 import static com.android.launcher3.LauncherState.FLAG_HIDE_BACK_BUTTON;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.NO_OFFSET;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE;
 import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
@@ -70,6 +72,7 @@
 import com.android.launcher3.util.DisplayController.NavigationMode;
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.ObjectWrapper;
+import com.android.launcher3.util.PendingSplitSelectInfo;
 import com.android.launcher3.util.RunnableList;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
 import com.android.launcher3.util.UiThreadHelper;
@@ -91,7 +94,10 @@
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.unfold.UnfoldTransitionFactory;
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
+import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig;
 import com.android.systemui.unfold.config.UnfoldTransitionConfig;
+import com.android.systemui.unfold.system.ActivityManagerActivityTypeProvider;
+import com.android.systemui.unfold.system.DeviceStateManagerFoldProvider;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -127,9 +133,19 @@
     private @Nullable UnfoldTransitionProgressProvider mUnfoldTransitionProgressProvider;
     private @Nullable LauncherUnfoldAnimationController mLauncherUnfoldAnimationController;
 
+    /**
+     * If Launcher restarted while in the middle of an Overview split select, it needs this data to
+     * recover. In all other cases this will remain null.
+     */
+    private PendingSplitSelectInfo mPendingSplitSelectInfo = null;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        if (savedInstanceState != null) {
+            mPendingSplitSelectInfo = ObjectWrapper.unwrap(
+                    savedInstanceState.getIBinder(PENDING_SPLIT_SELECT_INFO));
+        }
         addMultiWindowModeChangedListener(mDepthController);
         initUnfoldTransitionProgressProvider();
     }
@@ -321,7 +337,7 @@
                 new SplitSelectStateController(this, mHandler, getStateManager(),
                         getDepthController());
         overviewPanel.init(mActionsView, controller);
-        mActionsView.updateDimension(getDeviceProfile(), overviewPanel.getLastComputedTaskSize());
+        mActionsView.updateDimension(getDeviceProfile());
         mActionsView.updateVerticalMargin(DisplayController.getNavigationMode(this));
 
         mAppTransitionManager = new QuickstepTransitionManager(this);
@@ -343,15 +359,17 @@
     }
 
     private void initUnfoldTransitionProgressProvider() {
-        final UnfoldTransitionConfig config = UnfoldTransitionFactory.createConfig(this);
+        final UnfoldTransitionConfig config = new ResourceUnfoldTransitionConfig();
         if (config.isEnabled()) {
             mUnfoldTransitionProgressProvider =
                     UnfoldTransitionFactory.createUnfoldTransitionProgressProvider(
-                            this,
+                            /* context= */ this,
                             config,
                             ProxyScreenStatusProvider.INSTANCE,
-                            getSystemService(DeviceStateManager.class),
-                            getSystemService(ActivityManager.class),
+                            new DeviceStateManagerFoldProvider(
+                                    getSystemService(DeviceStateManager.class), /* context */this),
+                            new ActivityManagerActivityTypeProvider(
+                                    getSystemService(ActivityManager.class)),
                             getSystemService(SensorManager.class),
                             getMainThreadHandler(),
                             getMainExecutor(),
@@ -638,4 +656,53 @@
             mDepthController.dump(prefix, writer);
         }
     }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+
+        // If Launcher shuts downs during split select, we save some extra data in the recovery
+        // bundle to allow graceful recovery. The normal LauncherState restore mechanism doesn't
+        // work in this case because restoring straight to OverviewSplitSelect without staging data,
+        // or before the tasks themselves have loaded into Overview, causes a crash. So we tell
+        // Launcher to first restore into Overview state, wait for the relevant tasks and icons to
+        // load in, and then proceed to OverviewSplitSelect.
+        if (isInState(OVERVIEW_SPLIT_SELECT)) {
+            SplitSelectStateController splitSelectStateController =
+                    ((RecentsView) getOverviewPanel()).getSplitPlaceholder();
+            // Launcher will restart in Overview and then transition to OverviewSplitSelect.
+            outState.putIBinder(PENDING_SPLIT_SELECT_INFO, ObjectWrapper.wrap(
+                    new PendingSplitSelectInfo(
+                            splitSelectStateController.getInitialTaskId(),
+                            splitSelectStateController.getActiveSplitStagePosition()
+                    )
+            ));
+            outState.putInt(RUNTIME_STATE, OVERVIEW.ordinal);
+        }
+    }
+
+    /**
+     * When Launcher restarts, it sometimes needs to recover to a split selection state.
+     * This function checks if such a recovery is needed.
+     * @return a boolean representing whether the launcher is waiting to recover to
+     * OverviewSplitSelect state.
+     */
+    public boolean hasPendingSplitSelectInfo() {
+        return mPendingSplitSelectInfo != null;
+    }
+
+    /**
+     * See {@link #hasPendingSplitSelectInfo()}
+     */
+    public @Nullable PendingSplitSelectInfo getPendingSplitSelectInfo() {
+        return mPendingSplitSelectInfo;
+    }
+
+    /**
+     * When the launcher has successfully recovered to OverviewSplitSelect state, this function
+     * deletes the recovery data, returning it to a null state.
+     */
+    public void finishSplitSelectRecovery() {
+        mPendingSplitSelectInfo = null;
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index e1a3b72..bb79c1b 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -31,7 +31,6 @@
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.Utilities.mapBoundToRange;
-import static com.android.launcher3.Utilities.postAsyncCallback;
 import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
 import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_1_5;
@@ -184,6 +183,10 @@
     public static final int SPLIT_DIVIDER_ANIM_DURATION = 100;
 
     public static final int CONTENT_ALPHA_DURATION = 217;
+    public static final int TASKBAR_TO_APP_DURATION = 600;
+    // TODO(b/236145847): Tune TASKBAR_TO_HOME_DURATION to 383 after conflict with unlock animation
+    // is solved.
+    public static final int TASKBAR_TO_HOME_DURATION = 300;
     protected static final int CONTENT_SCALE_DURATION = 350;
     protected static final int CONTENT_SCRIM_DURATION = 350;
 
@@ -197,7 +200,6 @@
 
     final Handler mHandler;
 
-    private final float mContentScale;
     private final float mClosingWindowTransY;
     private final float mMaxShadowRadius;
 
@@ -242,7 +244,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);
 
@@ -480,8 +481,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();
@@ -527,7 +528,15 @@
             workspace.forEachVisiblePage(
                     view -> viewsToAnimate.add(((CellLayout) view).getShortcutsAndWidgets()));
 
-            viewsToAnimate.add(mLauncher.getHotseat());
+            // Do not scale hotseat as a whole when taskbar is present, and scale QSB only if it's
+            // not inline.
+            if (mDeviceProfile.isTaskbarPresent) {
+                if (!mDeviceProfile.isQsbInline) {
+                    viewsToAnimate.add(mLauncher.getHotseat().getQsb());
+                }
+            } else {
+                viewsToAnimate.add(mLauncher.getHotseat());
+            }
 
             viewsToAnimate.forEach(view -> {
                 view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
diff --git a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
index 0284ae4..f42b39f 100644
--- a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
@@ -21,7 +21,6 @@
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.graphics.Canvas;
-import android.graphics.Rect;
 import android.graphics.Typeface;
 import android.os.Build;
 import android.text.Layout;
@@ -33,7 +32,6 @@
 import androidx.annotation.ColorInt;
 import androidx.core.content.ContextCompat;
 
-import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.FloatingHeaderRow;
 import com.android.launcher3.allapps.FloatingHeaderView;
@@ -239,12 +237,6 @@
     }
 
     @Override
-    public void setInsets(Rect insets, DeviceProfile grid) {
-        int leftRightPadding = grid.allAppsLeftRightPadding;
-        setPadding(leftRightPadding, getPaddingTop(), leftRightPadding, getPaddingBottom());
-    }
-
-    @Override
     public void setVerticalScroll(int scroll, boolean isScrolledOut) {
         setTranslationY(scroll);
         mIsScrolledOut = isScrolledOut;
diff --git a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
index 1dec737..351a3bc 100644
--- a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -19,7 +19,6 @@
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.graphics.Canvas;
-import android.graphics.Rect;
 import android.os.Build;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
@@ -252,12 +251,6 @@
     }
 
     @Override
-    public void setInsets(Rect insets, DeviceProfile grid) {
-        int leftRightPadding = grid.allAppsLeftRightPadding;
-        setPadding(leftRightPadding, getPaddingTop(), leftRightPadding, getPaddingBottom());
-    }
-
-    @Override
     public Class<PredictionRowView> getTypeClass() {
         return PredictionRowView.class;
     }
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
index 119ae90..7b48332 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
@@ -41,7 +41,6 @@
 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,8 +106,7 @@
         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(
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index ca30e72..6df31e5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -171,7 +171,9 @@
                 isResumed,
                 fromInit,
                 /* startAnimation= */ true,
-                QuickstepTransitionManager.CONTENT_ALPHA_DURATION);
+                !isResumed
+                        ? QuickstepTransitionManager.TASKBAR_TO_APP_DURATION
+                        : QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION);
     }
 
     @Nullable
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 5d576f7..17da0d9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -35,6 +35,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
 import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_REGION;
 
 import android.animation.ArgbEvaluator;
@@ -105,8 +106,7 @@
     private static final int FLAG_DISABLE_BACK = 1 << 9;
     private static final int FLAG_NOTIFICATION_SHADE_EXPANDED = 1 << 10;
     private static final int FLAG_SCREEN_PINNING_ACTIVE = 1 << 11;
-
-    private static final int MASK_IME_SWITCHER_VISIBLE = FLAG_SWITCHER_SUPPORTED | FLAG_IME_VISIBLE;
+    private static final int FLAG_VOICE_INTERACTION_WINDOW_SHOWING = 1 << 12;
 
     private static final String NAV_BUTTONS_SEPARATE_WINDOW_TITLE = "Taskbar Nav Buttons";
 
@@ -189,7 +189,7 @@
                     isThreeButtonNav ? mStartContextualContainer : mEndContextualContainer,
                     mControllers.navButtonController, R.id.ime_switcher);
             mPropertyHolders.add(new StatePropertyHolder(imeSwitcherButton,
-                    flags -> ((flags & MASK_IME_SWITCHER_VISIBLE) == MASK_IME_SWITCHER_VISIBLE)
+                    flags -> ((flags & FLAG_SWITCHER_SUPPORTED) != 0)
                             && ((flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0)));
         }
 
@@ -207,9 +207,12 @@
         boolean isInKidsMode = mContext.isNavBarKidsModeActive();
         boolean alwaysShowButtons = isThreeButtonNav || isInSetup;
 
-        // Make sure to remove nav bar buttons translation when notification shade is expanded or
-        // IME is showing (add separate translation for IME).
-        int flagsToRemoveTranslation = FLAG_NOTIFICATION_SHADE_EXPANDED | FLAG_IME_VISIBLE;
+        // Make sure to remove nav bar buttons translation when any of the following occur:
+        // - Notification shade is expanded
+        // - IME is showing (add separate translation for IME)
+        // - VoiceInteractionWindow (assistant) is showing
+        int flagsToRemoveTranslation = FLAG_NOTIFICATION_SHADE_EXPANDED | FLAG_IME_VISIBLE
+                | FLAG_VOICE_INTERACTION_WINDOW_SHOWING;
         mPropertyHolders.add(new StatePropertyHolder(mNavButtonInAppDisplayProgressForSysui,
                 flags -> (flags & flagsToRemoveTranslation) != 0, AnimatedFloat.VALUE,
                 1, 0));
@@ -415,7 +418,7 @@
                     return recentsCoords;
                 }, new Handler());
         recentsButton.setOnClickListener(v -> {
-            navButtonController.onButtonClick(BUTTON_RECENTS);
+            navButtonController.onButtonClick(BUTTON_RECENTS, v);
             mHitboxExtender.onRecentsButtonClicked();
         });
         mPropertyHolders.add(new StatePropertyHolder(recentsButton,
@@ -443,6 +446,8 @@
                 | SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
         boolean isNotificationShadeExpanded = (sysUiStateFlags & shadeExpandedFlags) != 0;
         boolean isScreenPinningActive = (sysUiStateFlags & SYSUI_STATE_SCREEN_PINNING) != 0;
+        boolean isVoiceInteractionWindowShowing =
+                (sysUiStateFlags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0;
 
         // TODO(b/202218289) we're getting IME as not visible on lockscreen from system
         updateStateForFlag(FLAG_IME_VISIBLE, isImeVisible);
@@ -453,6 +458,7 @@
         updateStateForFlag(FLAG_DISABLE_BACK, isBackDisabled);
         updateStateForFlag(FLAG_NOTIFICATION_SHADE_EXPANDED, isNotificationShadeExpanded);
         updateStateForFlag(FLAG_SCREEN_PINNING_ACTIVE, isScreenPinningActive);
+        updateStateForFlag(FLAG_VOICE_INTERACTION_WINDOW_SHOWING, isVoiceInteractionWindowShowing);
 
         if (mA11yButton != null) {
             // Only used in 3 button
@@ -499,6 +505,13 @@
     }
 
     /**
+     * Returns true if IME switcher is visible
+     */
+    public boolean isImeSwitcherVisible() {
+        return (mState & FLAG_SWITCHER_SUPPORTED) != 0;
+    }
+
+    /**
      * Returns true if the home button is disabled
      */
     public boolean isHomeDisabled() {
@@ -626,9 +639,9 @@
         buttonView.setImageResource(drawableId);
         buttonView.setContentDescription(parent.getContext().getString(
                 navButtonController.getButtonContentDescription(buttonType)));
-        buttonView.setOnClickListener(view -> navButtonController.onButtonClick(buttonType));
+        buttonView.setOnClickListener(view -> navButtonController.onButtonClick(buttonType, view));
         buttonView.setOnLongClickListener(view ->
-                navButtonController.onButtonLongClick(buttonType));
+                navButtonController.onButtonLongClick(buttonType, view));
         return buttonView;
     }
 
@@ -750,6 +763,8 @@
         appendFlag(str, flags, FLAG_NOTIFICATION_SHADE_EXPANDED,
                 "FLAG_NOTIFICATION_SHADE_EXPANDED");
         appendFlag(str, flags, FLAG_SCREEN_PINNING_ACTIVE, "FLAG_SCREEN_PINNING_ACTIVE");
+        appendFlag(str, flags, FLAG_VOICE_INTERACTION_WINDOW_SHOWING,
+                "FLAG_VOICE_INTERACTION_WINDOW_SHOWING");
         return str.toString();
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
index b797807..f472427 100644
--- a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
@@ -43,7 +43,8 @@
 
     public static final int ALPHA_INDEX_STASHED = 0;
     public static final int ALPHA_INDEX_HOME_DISABLED = 1;
-    private static final int NUM_ALPHA_CHANNELS = 2;
+    public static final int ALPHA_INDEX_ASSISTANT_INVOKED = 2;
+    private static final int NUM_ALPHA_CHANNELS = 3;
 
     /**
      * The SharedPreferences key for whether the stashed handle region is dark.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index dc2e3b6..6b12e9c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -27,6 +27,7 @@
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_OPEN;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
 
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
@@ -200,7 +201,8 @@
                 new TaskbarPopupController(this),
                 new TaskbarForceVisibleImmersiveController(this),
                 new TaskbarAllAppsController(this, dp),
-                new TaskbarInsetsController(this));
+                new TaskbarInsetsController(this),
+                new VoiceInteractionWindowController(this));
     }
 
     public void init(@NonNull TaskbarSharedState sharedState) {
@@ -246,14 +248,23 @@
         return super.getStatsLogManager();
     }
 
-    /** Creates LayoutParams for adding a view directly to WindowManager as a new window */
+    /** @see #createDefaultWindowLayoutParams(int) */
     public WindowManager.LayoutParams createDefaultWindowLayoutParams() {
+        return createDefaultWindowLayoutParams(TYPE_NAVIGATION_BAR_PANEL);
+    }
+
+    /**
+     * Creates LayoutParams for adding a view directly to WindowManager as a new window.
+     * @param type The window type to pass to the created WindowManager.LayoutParams.
+     */
+    public WindowManager.LayoutParams createDefaultWindowLayoutParams(int type) {
         WindowManager.LayoutParams windowLayoutParams = new WindowManager.LayoutParams(
                 MATCH_PARENT,
                 mLastRequestedNonFullscreenHeight,
-                TYPE_NAVIGATION_BAR_PANEL,
+                type,
                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                        | WindowManager.LayoutParams.FLAG_SLIPPERY,
+                        | WindowManager.LayoutParams.FLAG_SLIPPERY
+                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
                 PixelFormat.TRANSLUCENT);
         windowLayoutParams.setTitle(WINDOW_TITLE);
         windowLayoutParams.packageName = getPackageName();
@@ -453,6 +464,8 @@
                 fromInit);
         mControllers.taskbarViewController.setImeIsVisible(
                 mControllers.navbarButtonsViewController.isImeVisible());
+        mControllers.taskbarViewController.setIsImeSwitcherVisible(
+                mControllers.navbarButtonsViewController.isImeSwitcherVisible());
         int shadeExpandedFlags = SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
                 | SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
         onNotificationShadeExpandChanged((systemUiStateFlags & shadeExpandedFlags) != 0, fromInit);
@@ -468,6 +481,8 @@
                 fromInit);
         mControllers.navButtonController.updateSysuiFlags(systemUiStateFlags);
         mControllers.taskbarForceVisibleImmersiveController.updateSysuiFlags(systemUiStateFlags);
+        mControllers.voiceInteractionWindowController.setIsVoiceInteractionWindowVisible(
+                (systemUiStateFlags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0, fromInit);
     }
 
     /**
@@ -612,7 +627,9 @@
 
     /** Removes the given view from WindowManager. See {@link #addWindowView}. */
     public void removeWindowView(View view) {
-        mWindowManager.removeViewImmediate(view);
+        if (view.isAttachedToWindow()) {
+            mWindowManager.removeViewImmediate(view);
+        }
     }
 
     protected void onTaskbarIconClicked(View view) {
@@ -822,6 +839,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/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
index 449e0a7..d7b50b0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -52,6 +52,7 @@
     public final TaskbarForceVisibleImmersiveController taskbarForceVisibleImmersiveController;
     public final TaskbarAllAppsController taskbarAllAppsController;
     public final TaskbarInsetsController taskbarInsetsController;
+    public final VoiceInteractionWindowController voiceInteractionWindowController;
 
     @Nullable private LoggableTaskbarController[] mControllersToLog = null;
 
@@ -80,7 +81,8 @@
             TaskbarPopupController taskbarPopupController,
             TaskbarForceVisibleImmersiveController taskbarForceVisibleImmersiveController,
             TaskbarAllAppsController taskbarAllAppsController,
-            TaskbarInsetsController taskbarInsetsController) {
+            TaskbarInsetsController taskbarInsetsController,
+            VoiceInteractionWindowController voiceInteractionWindowController) {
         this.taskbarActivityContext = taskbarActivityContext;
         this.taskbarDragController = taskbarDragController;
         this.navButtonController = navButtonController;
@@ -99,6 +101,7 @@
         this.taskbarForceVisibleImmersiveController = taskbarForceVisibleImmersiveController;
         this.taskbarAllAppsController = taskbarAllAppsController;
         this.taskbarInsetsController = taskbarInsetsController;
+        this.voiceInteractionWindowController = voiceInteractionWindowController;
     }
 
     /**
@@ -126,13 +129,15 @@
         taskbarAllAppsController.init(this, sharedState.allAppsVisible);
         navButtonController.init(this);
         taskbarInsetsController.init(this);
+        voiceInteractionWindowController.init(this);
 
         mControllersToLog = new LoggableTaskbarController[] {
                 taskbarDragController, navButtonController, navbarButtonsViewController,
                 taskbarDragLayerController, taskbarScrimViewController, taskbarViewController,
                 taskbarUnfoldAnimationController, taskbarKeyguardController,
                 stashedHandleViewController, taskbarStashController, taskbarEduController,
-                taskbarAutohideSuspendController, taskbarPopupController, taskbarInsetsController
+                taskbarAutohideSuspendController, taskbarPopupController, taskbarInsetsController,
+                voiceInteractionWindowController
         };
 
         mAreAllControllersInitialized = true;
@@ -172,6 +177,7 @@
         taskbarAllAppsController.onDestroy();
         navButtonController.onDestroy();
         taskbarInsetsController.onDestroy();
+        voiceInteractionWindowController.onDestroy();
 
         mControllersToLog = null;
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index c522888..04fcc44 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.taskbar;
 
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APPS;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
 
 import android.animation.Animator;
@@ -435,7 +436,7 @@
         if (tag instanceof ItemInfo) {
             ItemInfo item = (ItemInfo) tag;
             TaskbarViewController taskbarViewController = mControllers.taskbarViewController;
-            if (item.container == CONTAINER_ALL_APPS) {
+            if (item.container == CONTAINER_ALL_APPS || item.container == CONTAINER_PREDICTION) {
                 // Since all apps closes when the drag starts, target the all apps button instead.
                 target = taskbarViewController.getAllAppsButtonView();
             } else if (item.container >= 0) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
index 6a6a693..48fde8f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
@@ -17,8 +17,12 @@
 
 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.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
@@ -61,9 +65,6 @@
             )
         )
 
-        windowLayoutParams.providedInternalInsets = arrayOfNulls<Insets>(ITYPE_SIZE)
-        windowLayoutParams.providedInternalImeInsets = arrayOfNulls<Insets>(ITYPE_SIZE)
-
         onTaskbarWindowHeightOrInsetsChanged()
 
         windowLayoutParams.insetsRoundedCornerFrame = true
@@ -75,32 +76,35 @@
     }
 
     fun onTaskbarWindowHeightOrInsetsChanged() {
-        var reducingSize = getReducingInsetsForTaskbarInsetsHeight(
-            controllers.taskbarStashController.contentHeightToReportToApps)
+        var contentHeight = controllers.taskbarStashController.contentHeightToReportToApps
+        contentRegion.set(0, windowLayoutParams.height - contentHeight,
+            context.deviceProfile.widthPx, windowLayoutParams.height)
+        var tappableHeight = controllers.taskbarStashController.tappableHeightToReportToApps
+        for (provider in windowLayoutParams.providedInsets) {
+            if (provider.type == ITYPE_EXTRA_NAVIGATION_BAR) {
+                provider.insetsSize = Insets.of(0, 0, 0, contentHeight)
+            } else if (provider.type == ITYPE_BOTTOM_TAPPABLE_ELEMENT
+                      || provider.type == ITYPE_BOTTOM_MANDATORY_GESTURES) {
+                provider.insetsSize = Insets.of(0, 0, 0, tappableHeight)
+            }
+        }
 
-        contentRegion.set(0, reducingSize.top,
-                context.deviceProfile.widthPx, windowLayoutParams.height)
-        windowLayoutParams.providedInternalInsets[ITYPE_EXTRA_NAVIGATION_BAR] = reducingSize
-        windowLayoutParams.providedInternalInsets[ITYPE_BOTTOM_MANDATORY_GESTURES] = reducingSize
-        reducingSize = getReducingInsetsForTaskbarInsetsHeight(
-            controllers.taskbarStashController.tappableHeightToReportToApps)
-        windowLayoutParams.providedInternalInsets[ITYPE_BOTTOM_TAPPABLE_ELEMENT] = reducingSize
-        windowLayoutParams.providedInternalInsets[ITYPE_BOTTOM_MANDATORY_GESTURES] = reducingSize
-
-        reducingSize = getReducingInsetsForTaskbarInsetsHeight(taskbarHeightForIme)
-        windowLayoutParams.providedInternalImeInsets[ITYPE_EXTRA_NAVIGATION_BAR] = reducingSize
-        windowLayoutParams.providedInternalImeInsets[ITYPE_BOTTOM_TAPPABLE_ELEMENT] = reducingSize
-        windowLayoutParams.providedInternalImeInsets[ITYPE_BOTTOM_MANDATORY_GESTURES] = reducingSize
-    }
-
-    /**
-     * WindowLayoutParams.providedInternal*Insets expects Insets that subtract from the window frame
-     * height (i.e. WindowLayoutParams#height). So for Taskbar to report bottom insets to apps, it
-     * actually provides insets from the top of its window frame.
-     * @param height The number of pixels from the bottom of the screen that Taskbar insets.
-     */
-    private fun getReducingInsetsForTaskbarInsetsHeight(height: Int): Insets {
-        return Insets.of(0, windowLayoutParams.height - height, 0, 0)
+        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
+        }
     }
 
     /**
@@ -151,13 +155,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/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index dc0ef27..ff11f67 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -19,11 +19,13 @@
 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_STASHED_LAUNCHER_STATE;
 import static com.android.launcher3.taskbar.TaskbarStashController.TASKBAR_STASH_DURATION;
 import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_HOME;
+import static com.android.systemui.animation.Interpolators.EMPHASIZED;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
+import android.util.Log;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -31,6 +33,7 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.QuickstepTransitionManager;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.util.MultiValueAlpha;
@@ -44,7 +47,6 @@
 import java.io.PrintWriter;
 import java.util.HashMap;
 import java.util.StringJoiner;
-import java.util.function.Consumer;
 import java.util.function.Supplier;
 
 /**
@@ -53,6 +55,9 @@
  */
  public class TaskbarLauncherStateController {
 
+    private static final String TAG = TaskbarLauncherStateController.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
     public static final int FLAG_RESUMED = 1 << 0;
     public static final int FLAG_RECENTS_ANIMATION_RUNNING = 1 << 1;
     public static final int FLAG_TRANSITION_STATE_RUNNING = 1 << 2;
@@ -99,7 +104,11 @@
                     }
                     updateStateForFlag(FLAG_TRANSITION_STATE_RUNNING, true);
                     if (!mShouldDelayLauncherStateAnim) {
-                        applyState();
+                        if (toState == LauncherState.NORMAL) {
+                            applyState(QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION);
+                        } else {
+                            applyState();
+                        }
                     }
                 }
 
@@ -122,7 +131,12 @@
         MultiValueAlpha taskbarIconAlpha = mControllers.taskbarViewController.getTaskbarIconAlpha();
         mIconAlphaForHome = taskbarIconAlpha.getProperty(ALPHA_INDEX_HOME);
         mIconAlphaForHome.setConsumer(
-                (Consumer<Float>) alpha -> mLauncher.getHotseat().setIconsAlpha(alpha > 0 ? 0 : 1));
+                alpha -> {
+                    mLauncher.getHotseat().setIconsAlpha(alpha > 0 ? 0 : 1);
+                    if (mLauncher.getDeviceProfile().isQsbInline) {
+                        mLauncher.getHotseat().setQsbAlpha(alpha > 0 ? 0 : 1);
+                    }
+                });
 
         mIconAlignmentForResumedState.finishAnimation();
         onIconAlignmentRatioChangedForAppAndHomeTransition();
@@ -169,10 +183,8 @@
 
         mTaskBarRecentsAnimationListener = new TaskBarRecentsAnimationListener(callbacks);
         callbacks.addListener(mTaskBarRecentsAnimationListener);
-        RecentsView recentsView = mLauncher.getOverviewPanel();
-        recentsView.setTaskLaunchListener(() -> {
-            mTaskBarRecentsAnimationListener.endGestureStateOverride(true);
-        });
+        ((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchListener(() ->
+                mTaskBarRecentsAnimationListener.endGestureStateOverride(true));
         return animatorSet;
     }
 
@@ -269,6 +281,11 @@
                 ObjectAnimator resumeAlignAnim = mIconAlignmentForResumedState
                         .animateToValue(toAlignmentForResumedState)
                         .setDuration(duration);
+                if (DEBUG) {
+                    Log.d(TAG, "mIconAlignmentForResumedState - "
+                            + mIconAlignmentForResumedState.value
+                            + " -> " + toAlignmentForResumedState + ": " + duration);
+                }
 
                 resumeAlignAnim.addListener(new AnimatorListenerAdapter() {
                     @Override
@@ -305,6 +322,11 @@
                 if (isRecentsAnimationRunning) {
                     gestureAlignAnim.setDuration(duration);
                 }
+                if (DEBUG) {
+                    Log.d(TAG, "mIconAlignmentForGestureState - "
+                            + mIconAlignmentForGestureState.value
+                            + " -> " + toAlignmentForGestureState + ": " + duration);
+                }
                 gestureAlignAnim.addListener(new AnimatorListenerAdapter() {
                     @Override
                     public void onAnimationEnd(Animator animation) {
@@ -330,6 +352,7 @@
                     .setDuration(duration));
         }
 
+        animatorSet.setInterpolator(EMPHASIZED);
         if (start) {
             animatorSet.start();
         }
@@ -374,6 +397,12 @@
             mIconAlignmentForLauncherState.finishAnimation();
             animatorSet.play(mIconAlignmentForLauncherState.animateToValue(toAlignment)
                     .setDuration(duration));
+            if (DEBUG) {
+                Log.d(TAG, "mIconAlignmentForLauncherState - "
+                        + mIconAlignmentForLauncherState.value
+                        + " -> " + toAlignment + ": " + duration);
+            }
+            animatorSet.setInterpolator(EMPHASIZED);
         }
     }
 
@@ -396,17 +425,17 @@
         onIconAlignmentRatioChanged(this::getCurrentIconAlignmentRatioBetweenAppAndHome);
     }
 
-    private void onIconAlignmentRatioChanged(Supplier<Float> alignmentSupplier) {
+    private void onIconAlignmentRatioChanged(Supplier<AnimatedFloat> alignmentSupplier) {
         if (mControllers == null) {
             return;
         }
-        float alignment = alignmentSupplier.get();
+        AnimatedFloat animatedFloat = alignmentSupplier.get();
         float currentValue = mIconAlphaForHome.getValue();
-        boolean taskbarWillBeVisible = alignment < 1;
+        boolean taskbarWillBeVisible = animatedFloat.value < 1;
         boolean firstFrameVisChanged = (taskbarWillBeVisible && Float.compare(currentValue, 1) != 0)
                 || (!taskbarWillBeVisible && Float.compare(currentValue, 0) != 0);
 
-        updateIconAlignment(alignment);
+        updateIconAlignment(animatedFloat.value, animatedFloat.getEndValue());
 
         // Sync the first frame where we swap taskbar and hotseat.
         if (firstFrameVisChanged && mCanSyncViews && !Utilities.IS_RUNNING_IN_TEST_HARNESS) {
@@ -416,21 +445,22 @@
         }
     }
 
-    private void updateIconAlignment(float alignment) {
+    private void updateIconAlignment(float alignment, Float endAlignment) {
         mControllers.taskbarViewController.setLauncherIconAlignment(
-                alignment, mLauncher.getDeviceProfile());
+                alignment, endAlignment, mLauncher.getDeviceProfile());
 
         // Switch taskbar and hotseat in last frame
         setTaskbarViewVisible(alignment < 1);
         mControllers.navbarButtonsViewController.updateTaskbarAlignment(alignment);
     }
 
-    private float getCurrentIconAlignmentRatioBetweenAppAndHome() {
-        return Math.max(mIconAlignmentForResumedState.value, mIconAlignmentForGestureState.value);
+    private AnimatedFloat getCurrentIconAlignmentRatioBetweenAppAndHome() {
+        return mIconAlignmentForResumedState.value > mIconAlignmentForGestureState.value
+                ? mIconAlignmentForResumedState : mIconAlignmentForGestureState;
     }
 
-    private float getCurrentIconAlignmentRatioForLauncherState() {
-        return mIconAlignmentForLauncherState.value;
+    private AnimatedFloat getCurrentIconAlignmentRatioForLauncherState() {
+        return mIconAlignmentForLauncherState;
     }
 
     private void setTaskbarViewVisible(boolean isVisible) {
@@ -459,6 +489,7 @@
         private void endGestureStateOverride(boolean finishedToApp) {
             mCallbacks.removeListener(this);
             mTaskBarRecentsAnimationListener = null;
+            ((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchListener(null);
 
             // Update the resumed state immediately to ensure a seamless handoff
             boolean launcherResumed = !finishedToApp;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
index 4ff0649..3392b6b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
@@ -33,6 +33,8 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.util.Log;
+import android.view.HapticFeedbackConstants;
+import android.view.View;
 
 import androidx.annotation.IntDef;
 import androidx.annotation.Nullable;
@@ -113,7 +115,9 @@
         mHandler = handler;
     }
 
-    public void onButtonClick(@TaskbarButton int buttonType) {
+    public void onButtonClick(@TaskbarButton int buttonType, View view) {
+        // Provide the same haptic feedback that the system offers for virtual keys.
+        view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
         switch (buttonType) {
             case BUTTON_BACK:
                 logEvent(LAUNCHER_TASKBAR_BACK_BUTTON_TAP);
@@ -144,7 +148,9 @@
         }
     }
 
-    public boolean onButtonLongClick(@TaskbarButton int buttonType) {
+    public boolean onButtonLongClick(@TaskbarButton int buttonType, View view) {
+        // Provide the same haptic feedback that the system offers for virtual keys.
+        view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
         switch (buttonType) {
             case BUTTON_HOME:
                 logEvent(LAUNCHER_TASKBAR_HOME_BUTTON_LONGPRESS);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index fc9f9d0..2b8fdd1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -22,6 +22,7 @@
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_SHOW;
 import static com.android.launcher3.taskbar.Utilities.appendFlag;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
 
 import android.animation.Animator;
@@ -30,13 +31,15 @@
 import android.annotation.Nullable;
 import android.content.SharedPreferences;
 import android.util.Log;
+import android.view.View;
 import android.view.ViewConfiguration;
-import android.view.WindowInsets;
 
+import androidx.annotation.NonNull;
+
+import com.android.internal.jank.InteractionJankMonitor;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorListeners;
-import com.android.launcher3.taskbar.allapps.TaskbarAllAppsSlideInView;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
 import com.android.quickstep.AnimatedFloat;
@@ -44,6 +47,8 @@
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
 import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Optional;
 import java.util.StringJoiner;
 import java.util.function.IntPredicate;
 
@@ -53,6 +58,8 @@
  */
 public class TaskbarStashController implements TaskbarControllers.LoggableTaskbarController {
 
+    private static final String TAG = "TaskbarStashController";
+
     public static final int FLAG_IN_APP = 1 << 0;
     public static final int FLAG_STASHED_IN_APP_MANUAL = 1 << 1; // long press, persisted
     public static final int FLAG_STASHED_IN_APP_PINNED = 1 << 2; // app pinning
@@ -149,6 +156,7 @@
     private @Nullable AnimatorSet mAnimator;
     private boolean mIsSystemGestureInProgress;
     private boolean mIsImeShowing;
+    private boolean mIsImeSwitcherShowing;
 
     private boolean mEnableManualStashingForTests = false;
 
@@ -401,6 +409,7 @@
             mAnimator.cancel();
         }
         mAnimator = new AnimatorSet();
+        addJankMonitorListener(mAnimator, /* appearing= */ !mIsStashed);
 
         if (!supportsVisualStashing()) {
             // Just hide/show the icons and background instead of stashing into a handle.
@@ -496,6 +505,28 @@
         });
     }
 
+    private void addJankMonitorListener(AnimatorSet animator, boolean expanding) {
+        Optional<View> optionalView =
+                Arrays.stream(mControllers.taskbarViewController.getIconViews()).findFirst();
+        if (optionalView.isEmpty()) {
+            Log.wtf(TAG, "No views to start Interaction jank monitor with.", new Exception());
+            return;
+        }
+        View v = optionalView.get();
+        int action = expanding ? InteractionJankMonitor.CUJ_TASKBAR_EXPAND :
+                InteractionJankMonitor.CUJ_TASKBAR_COLLAPSE;
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(@NonNull Animator animation) {
+                InteractionJankMonitor.getInstance().begin(v, action);
+            }
+
+            @Override
+            public void onAnimationEnd(@NonNull Animator animation) {
+                InteractionJankMonitor.getInstance().end(action);
+            }
+        });
+    }
     /**
      * Creates and starts a partial stash animation, hinting at the new state that will trigger when
      * long press is detected.
@@ -571,9 +602,11 @@
         }
 
         // Only update the following flags when system gesture is not in progress.
-        maybeResetStashedInAppAllApps(hasAnyFlag(FLAG_STASHED_IN_APP_IME) == mIsImeShowing);
-        if (hasAnyFlag(FLAG_STASHED_IN_APP_IME) != mIsImeShowing) {
-            updateStateForFlag(FLAG_STASHED_IN_APP_IME, mIsImeShowing);
+        boolean shouldStashForIme = shouldStashForIme();
+        maybeResetStashedInAppAllApps(
+                hasAnyFlag(FLAG_STASHED_IN_APP_IME) == shouldStashForIme);
+        if (hasAnyFlag(FLAG_STASHED_IN_APP_IME) != shouldStashForIme) {
+            updateStateForFlag(FLAG_STASHED_IN_APP_IME, shouldStashForIme);
             applyState(TASKBAR_STASH_DURATION_FOR_IME, getTaskbarStashStartDelayForIme());
         }
     }
@@ -625,8 +658,9 @@
 
         // Only update FLAG_STASHED_IN_APP_IME when system gesture is not in progress.
         mIsImeShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SHOWING);
+        mIsImeSwitcherShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SWITCHER_SHOWING);
         if (!mIsSystemGestureInProgress) {
-            updateStateForFlag(FLAG_STASHED_IN_APP_IME, mIsImeShowing);
+            updateStateForFlag(FLAG_STASHED_IN_APP_IME, shouldStashForIme());
             animDuration = TASKBAR_STASH_DURATION_FOR_IME;
             startDelay = getTaskbarStashStartDelayForIme();
         }
@@ -634,6 +668,10 @@
         applyState(skipAnim ? 0 : animDuration, skipAnim ? 0 : startDelay);
     }
 
+    private boolean shouldStashForIme() {
+        return mIsImeShowing || mIsImeSwitcherShowing;
+    }
+
     /**
      * Updates the proper flag to indicate whether the task bar should be stashed.
      *
@@ -699,6 +737,7 @@
         pw.println(String.format(
                 "%s\tmIsSystemGestureInProgress=%b", prefix, mIsSystemGestureInProgress));
         pw.println(String.format("%s\tmIsImeShowing=%b", prefix, mIsImeShowing));
+        pw.println(String.format("%s\tmIsImeSwitcherShowing=%b", prefix, mIsImeSwitcherShowing));
     }
 
     private static String getStateString(int flags) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 6f88d64..ea15acb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -20,6 +20,7 @@
 import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.util.AttributeSet;
+import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
@@ -31,6 +32,7 @@
 import androidx.core.graphics.ColorUtils;
 
 import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
@@ -40,7 +42,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;
@@ -78,6 +79,8 @@
     // Only non-null when device supports having an All Apps button.
     private @Nullable AllAppsButton mAllAppsButton;
 
+    private View mQsb;
+
     public TaskbarView(@NonNull Context context) {
         this(context, null);
     }
@@ -117,6 +120,9 @@
                     new ViewGroup.LayoutParams(mIconTouchSize, mIconTouchSize));
             mAllAppsButton.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding);
         }
+
+        // TODO: Disable touch events on QSB otherwise it can crash.
+        mQsb = LayoutInflater.from(context).inflate(R.layout.search_container_hotseat, this, false);
     }
 
     private int getColorWithGivenLuminance(int color, float luminance) {
@@ -166,6 +172,7 @@
         if (mAllAppsButton != null) {
             removeView(mAllAppsButton);
         }
+        removeView(mQsb);
 
         for (int i = 0; i < hotseatItemInfos.length; i++) {
             ItemInfo hotseatItemInfo = hotseatItemInfos[i];
@@ -242,6 +249,11 @@
             int index = Utilities.isRtl(getResources()) ? 0 : getChildCount();
             addView(mAllAppsButton, index);
         }
+        if (mActivityContext.getDeviceProfile().isQsbInline) {
+            addView(mQsb, Utilities.isRtl(getResources()) ? getChildCount() : 0);
+            // Always set QSB to invisible after re-adding.
+            mQsb.setVisibility(View.INVISIBLE);
+        }
 
         mThemeIconsBackground = calculateThemeIconsBackground();
         setThemedIconsBackgroundColor(mThemeIconsBackground);
@@ -273,8 +285,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 +309,25 @@
         mIconLayoutBounds.bottom = mIconLayoutBounds.top + mIconTouchSize;
         for (int i = count; i > 0; i--) {
             View child = getChildAt(i - 1);
-            iconEnd -= mItemMarginLeftRight;
-            int iconStart = iconEnd - mIconTouchSize;
-            child.layout(iconStart, mIconLayoutBounds.top, iconEnd, mIconLayoutBounds.bottom);
-            iconEnd = iconStart - mItemMarginLeftRight;
+            if (child == mQsb) {
+                int qsbStart;
+                int qsbEnd;
+                if (layoutRtl) {
+                    qsbStart = iconEnd + mItemMarginLeftRight;
+                    qsbEnd = qsbStart + deviceProfile.qsbWidth;
+                } else {
+                    qsbEnd = iconEnd - mItemMarginLeftRight;
+                    qsbStart = qsbEnd - deviceProfile.qsbWidth;
+                }
+                int qsbTop = (bottom - top - deviceProfile.hotseatQsbHeight) / 2;
+                int qsbBottom = qsbTop + deviceProfile.hotseatQsbHeight;
+                child.layout(qsbStart, qsbTop, qsbEnd, qsbBottom);
+            } else {
+                iconEnd -= mItemMarginLeftRight;
+                int iconStart = iconEnd - mIconTouchSize;
+                child.layout(iconStart, mIconLayoutBounds.top, iconEnd, mIconLayoutBounds.bottom);
+                iconEnd = iconStart - mItemMarginLeftRight;
+            }
         }
         mIconLayoutBounds.left = iconEnd;
     }
@@ -367,6 +399,13 @@
         return mAllAppsButton;
     }
 
+    /**
+     * Returns the QSB in the taskbar.
+     */
+    public View getQsb() {
+        return mQsb;
+    }
+
     // FolderIconParent implemented methods.
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 3562f5b..db7dc78 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.taskbar;
 
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
 import static com.android.launcher3.Utilities.squaredHypot;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP;
@@ -35,12 +36,15 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AlphaUpdateListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.icons.ThemedIconDrawable;
 import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.util.HorizontalInsettableView;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.LauncherBindableItemsContainer;
 import com.android.launcher3.util.MultiValueAlpha;
@@ -63,7 +67,9 @@
     public static final int ALPHA_INDEX_STASH = 2;
     public static final int ALPHA_INDEX_RECENTS_DISABLED = 3;
     public static final int ALPHA_INDEX_NOTIFICATION_EXPANDED = 4;
-    private static final int NUM_ALPHA_CHANNELS = 5;
+    public static final int ALPHA_INDEX_ASSISTANT_INVOKED = 5;
+    public static final int ALPHA_INDEX_IME_BUTTON_NAV = 6;
+    private static final int NUM_ALPHA_CHANNELS = 7;
 
     private final TaskbarActivityContext mActivity;
     private final TaskbarView mTaskbarView;
@@ -138,6 +144,14 @@
     }
 
     /**
+     * Should be called when the IME switcher visibility changes.
+     */
+    public void setIsImeSwitcherVisible(boolean isImeSwitcherVisible) {
+        mTaskbarIconAlpha.getProperty(ALPHA_INDEX_IME_BUTTON_NAV).setValue(
+                isImeSwitcherVisible ? 0 : 1);
+    }
+
+    /**
      * Should be called when the recents button is disabled, so we can hide taskbar icons as well.
      */
     public void setRecentsButtonDisabled(boolean isDisabled) {
@@ -210,9 +224,10 @@
      *                       0 => not aligned
      *                       1 => fully aligned
      */
-    public void setLauncherIconAlignment(float alignmentRatio, DeviceProfile launcherDp) {
+    public void setLauncherIconAlignment(float alignmentRatio, Float endAlignment,
+            DeviceProfile launcherDp) {
         if (mIconAlignControllerLazy == null) {
-            mIconAlignControllerLazy = createIconAlignmentController(launcherDp);
+            mIconAlignControllerLazy = createIconAlignmentController(launcherDp, endAlignment);
         }
         mIconAlignControllerLazy.setPlayFraction(alignmentRatio);
         if (alignmentRatio <= 0 || alignmentRatio >= 1) {
@@ -224,11 +239,13 @@
     /**
      * Creates an animation for aligning the taskbar icons with the provided Launcher device profile
      */
-    private AnimatorPlaybackController createIconAlignmentController(DeviceProfile launcherDp) {
+    private AnimatorPlaybackController createIconAlignmentController(DeviceProfile launcherDp,
+            Float endAlignment) {
         mOnControllerPreCreateCallback.run();
         PendingAnimation setter = new PendingAnimation(100);
+        DeviceProfile taskbarDp = mActivity.getDeviceProfile();
         Rect hotseatPadding = launcherDp.getHotseatLayoutPadding(mActivity);
-        float scaleUp = ((float) launcherDp.iconSizePx) / mActivity.getDeviceProfile().iconSizePx;
+        float scaleUp = ((float) launcherDp.iconSizePx) / taskbarDp.iconSizePx;
         int borderSpacing = launcherDp.hotseatBorderSpace;
         int hotseatCellSize = DeviceProfile.calculateCellWidth(
                 launcherDp.availableWidthPx - hotseatPadding.left - hotseatPadding.right,
@@ -245,14 +262,13 @@
         }
 
         int collapsedHeight = mActivity.getDefaultTaskbarWindowHeight();
-        int expandedHeight = Math.max(collapsedHeight,
-                mActivity.getDeviceProfile().taskbarSize + offsetY);
+        int expandedHeight = Math.max(collapsedHeight, taskbarDp.taskbarSize + offsetY);
         setter.addOnFrameListener(anim -> mActivity.setTaskbarWindowHeight(
                 anim.getAnimatedFraction() > 0 ? expandedHeight : collapsedHeight));
 
+        boolean isToHome = endAlignment != null && endAlignment == 1;
         for (int i = 0; i < mTaskbarView.getChildCount(); i++) {
             View child = mTaskbarView.getChildAt(i);
-
             int positionInHotseat;
             if (FeatureFlags.ENABLE_ALL_APPS_IN_TASKBAR.get()
                     && child == mTaskbarView.getAllAppsButtonView()) {
@@ -260,13 +276,44 @@
                 // as its convenient for animation purposes.
                 positionInHotseat = Utilities.isRtl(child.getResources())
                         ? -1
-                        : mActivity.getDeviceProfile().numShownHotseatIcons;
+                        : taskbarDp.numShownHotseatIcons;
 
                 if (!FeatureFlags.ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT.get()) {
-                    setter.setViewAlpha(child, 0, LINEAR);
+                    setter.setViewAlpha(child, 0,
+                            isToHome
+                                    ? Interpolators.clampToProgress(LINEAR, 0f, 0.17f)
+                                    : Interpolators.clampToProgress(LINEAR, 0.72f, 0.84f));
                 }
             } else if (child.getTag() instanceof ItemInfo) {
                 positionInHotseat = ((ItemInfo) child.getTag()).screenId;
+            } else if (child == mTaskbarView.getQsb()) {
+                boolean isRtl = Utilities.isRtl(child.getResources());
+                float hotseatIconCenter = isRtl
+                        ? launcherDp.widthPx - hotseatPadding.right + borderSpacing
+                        + launcherDp.qsbWidth / 2f
+                        : hotseatPadding.left - borderSpacing - launcherDp.qsbWidth / 2f;
+                float childCenter = (child.getLeft() + child.getRight()) / 2f;
+                float halfQsbIconWidthDiff = (launcherDp.qsbWidth - taskbarDp.iconSizePx) / 2f;
+                setter.addFloat(child, ICON_TRANSLATE_X,
+                        isRtl ? -halfQsbIconWidthDiff : halfQsbIconWidthDiff,
+                        hotseatIconCenter - childCenter, LINEAR);
+
+                float scale = ((float) taskbarDp.iconSizePx) / launcherDp.hotseatQsbVisualHeight;
+                setter.addFloat(child, SCALE_PROPERTY, scale, 1f, LINEAR);
+
+                setter.addFloat(child, VIEW_ALPHA, 0f, 1f,
+                        isToHome
+                                ? Interpolators.clampToProgress(LINEAR, 0f, 0.35f)
+                                : Interpolators.clampToProgress(LINEAR, 0.84f, 1f));
+                setter.addOnFrameListener(animator -> AlphaUpdateListener.updateVisibility(child));
+
+                float qsbInsetFraction = halfQsbIconWidthDiff / launcherDp.qsbWidth;
+                if (child instanceof  HorizontalInsettableView) {
+                    setter.addFloat((HorizontalInsettableView) child,
+                            HorizontalInsettableView.HORIZONTAL_INSETS, qsbInsetFraction, 0,
+                            LINEAR);
+                }
+                continue;
             } else {
                 Log.w(TAG, "Unsupported view found in createIconAlignmentController, v=" + child);
                 continue;
diff --git a/quickstep/src/com/android/launcher3/taskbar/VoiceInteractionWindowController.kt b/quickstep/src/com/android/launcher3/taskbar/VoiceInteractionWindowController.kt
new file mode 100644
index 0000000..946873e
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/VoiceInteractionWindowController.kt
@@ -0,0 +1,109 @@
+package com.android.launcher3.taskbar
+
+import android.graphics.Canvas
+import android.view.WindowManager
+import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
+import com.android.launcher3.views.BaseDragLayer
+import com.android.systemui.animation.ViewRootSync
+import java.io.PrintWriter
+
+private const val TASKBAR_ICONS_FADE_DURATION = 300L
+private const val STASHED_HANDLE_FADE_DURATION = 180L
+
+/**
+ * Controls Taskbar behavior while Voice Interaction Window (assistant) is showing.
+ */
+class VoiceInteractionWindowController(val context: TaskbarActivityContext)
+    : TaskbarControllers.LoggableTaskbarController {
+
+    private val taskbarBackgroundRenderer = TaskbarBackgroundRenderer(context)
+
+    // Initialized in init.
+    private lateinit var controllers: TaskbarControllers
+    private lateinit var separateWindowForTaskbarBackground: BaseDragLayer<TaskbarActivityContext>
+    private lateinit var separateWindowLayoutParams: WindowManager.LayoutParams
+
+    private var isVoiceInteractionWindowVisible: Boolean = false
+
+    fun init(controllers: TaskbarControllers) {
+        this.controllers = controllers
+
+        separateWindowForTaskbarBackground =
+            object : BaseDragLayer<TaskbarActivityContext>(context, null, 0) {
+                override fun recreateControllers() {
+                    mControllers = emptyArray()
+                }
+
+                override fun draw(canvas: Canvas) {
+                    super.draw(canvas)
+                    taskbarBackgroundRenderer.draw(canvas)
+                }
+            }
+        separateWindowForTaskbarBackground.recreateControllers()
+        separateWindowForTaskbarBackground.setWillNotDraw(false)
+
+        separateWindowLayoutParams = context.createDefaultWindowLayoutParams(
+            TYPE_APPLICATION_OVERLAY)
+        separateWindowLayoutParams.isSystemApplicationOverlay = true
+    }
+
+    fun onDestroy() {
+        setIsVoiceInteractionWindowVisible(visible = false, skipAnim = true)
+    }
+
+    fun setIsVoiceInteractionWindowVisible(visible: Boolean, skipAnim: Boolean) {
+        if (isVoiceInteractionWindowVisible == visible) {
+            return
+        }
+        isVoiceInteractionWindowVisible = visible
+
+        // Fade out taskbar icons and stashed handle.
+        val taskbarIconAlpha = if (isVoiceInteractionWindowVisible) 0f else 1f
+        val fadeTaskbarIcons = controllers.taskbarViewController.taskbarIconAlpha
+            .getProperty(TaskbarViewController.ALPHA_INDEX_ASSISTANT_INVOKED)
+            .animateToValue(taskbarIconAlpha)
+            .setDuration(TASKBAR_ICONS_FADE_DURATION)
+        val fadeStashedHandle = controllers.stashedHandleViewController.stashedHandleAlpha
+            .getProperty(StashedHandleViewController.ALPHA_INDEX_ASSISTANT_INVOKED)
+            .animateToValue(taskbarIconAlpha)
+            .setDuration(STASHED_HANDLE_FADE_DURATION)
+        fadeTaskbarIcons.start()
+        fadeStashedHandle.start()
+        if (skipAnim) {
+            fadeTaskbarIcons.end()
+            fadeStashedHandle.end()
+        }
+
+        if (context.isGestureNav && controllers.taskbarStashController.isInAppAndNotStashed) {
+            moveTaskbarBackgroundToLowerLayer()
+        }
+    }
+
+    /**
+     * Hides the TaskbarDragLayer background and creates a new window to draw just that background.
+     */
+    private fun moveTaskbarBackgroundToLowerLayer() {
+        val taskbarBackgroundOverride = controllers.taskbarDragLayerController
+            .overrideBackgroundAlpha
+        if (isVoiceInteractionWindowVisible) {
+            // First add the temporary window, then hide the overlapping taskbar background.
+            context.addWindowView(separateWindowForTaskbarBackground, separateWindowLayoutParams)
+            ViewRootSync.synchronizeNextDraw(separateWindowForTaskbarBackground, context.dragLayer
+            ) {
+                taskbarBackgroundOverride.updateValue(0f)
+            }
+        } else {
+            // First reapply the original taskbar background, then remove the temporary window.
+            taskbarBackgroundOverride.updateValue(1f)
+            ViewRootSync.synchronizeNextDraw(separateWindowForTaskbarBackground, context.dragLayer
+            ) {
+                context.removeWindowView(separateWindowForTaskbarBackground)
+            }
+        }
+    }
+
+    override fun dumpLogs(prefix: String, pw: PrintWriter) {
+        pw.println(prefix + "VoiceInteractionWindowController:")
+        pw.println("$prefix\tisVoiceInteractionWindowVisible=$isVoiceInteractionWindowVisible")
+    }
+}
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
index 6fd98db..6c43e50 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
@@ -171,8 +171,8 @@
      * This method should be called after an exit animation finishes, if applicable.
      */
     void maybeCloseWindow() {
-        if (AbstractFloatingView.getOpenView(mAllAppsContext, TYPE_ALL) != null
-                || mAllAppsContext.getDragController().isSystemDragInProgress()) {
+        if (mAllAppsContext != null && (AbstractFloatingView.hasOpenView(mAllAppsContext, TYPE_ALL)
+                || mAllAppsContext.getDragController().isSystemDragInProgress())) {
             return;
         }
         mProxyView.close(false);
@@ -187,7 +187,7 @@
         TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
         Optional.ofNullable(mAllAppsContext)
                 .map(c -> c.getSystemService(WindowManager.class))
-                .ifPresent(m -> m.removeView(mAllAppsContext.getDragLayer()));
+                .ifPresent(m -> m.removeViewImmediate(mAllAppsContext.getDragLayer()));
         mAllAppsContext = null;
     }
 
@@ -207,7 +207,7 @@
     private LayoutParams createLayoutParams() {
         LayoutParams layoutParams = new LayoutParams(
                 TYPE_APPLICATION_OVERLAY,
-                0,
+                WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
                 PixelFormat.TRANSLUCENT);
         layoutParams.setTitle(WINDOW_TITLE);
         layoutParams.gravity = Gravity.BOTTOM;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/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..7166a3e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -28,6 +28,7 @@
 import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
 import static com.android.launcher3.testing.TestProtocol.BAD_STATE;
 import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
+import static com.android.quickstep.views.RecentsView.OVERVIEW_PROGRESS;
 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;
@@ -74,6 +75,7 @@
         getTaskModalnessProperty().set(mRecentsView, state.getOverviewModalness());
         RECENTS_GRID_PROGRESS.set(mRecentsView,
                 state.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile()) ? 1f : 0f);
+        OVERVIEW_PROGRESS.set(mRecentsView, state == LauncherState.OVERVIEW ? 1f : 0f);
     }
 
     @Override
@@ -117,6 +119,10 @@
         boolean showAsGrid = toState.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile());
         setter.setFloat(mRecentsView, RECENTS_GRID_PROGRESS, showAsGrid ? 1f : 0f,
                 showAsGrid ? INSTANT : FINAL_FRAME);
+
+        boolean toOverview = toState == LauncherState.OVERVIEW;
+        setter.setFloat(mRecentsView, OVERVIEW_PROGRESS, toOverview ? 1f : 0f,
+                toOverview ? INSTANT : FINAL_FRAME);
     }
 
     abstract FloatProperty getTaskModalnessProperty();
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index a74774c..9f2efc4 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -31,8 +31,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;
 
@@ -60,7 +58,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 +70,7 @@
             ScaleAndTranslation overviewScaleAndTranslation = LauncherState.OVERVIEW
                     .getWorkspaceScaleAndTranslation(launcher);
             return new ScaleAndTranslation(
-                    WORKSPACE_SCALE_FACTOR,
+                    launcher.getDeviceProfile().workspaceContentScale,
                     overviewScaleAndTranslation.translationX,
                     overviewScaleAndTranslation.translationY);
         }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index b733007..297a61a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -50,8 +50,7 @@
             return super.getVerticalProgress(launcher);
         }
         RecentsView recentsView = launcher.getOverviewPanel();
-        int transitionLength = LayoutUtils.getShelfTrackingDistance(launcher,
-                launcher.getDeviceProfile(),
+        int transitionLength = LayoutUtils.getShelfTrackingDistance(launcher.getDeviceProfile(),
                 recentsView.getPagedOrientationHandler());
         AllAppsTransitionController controller = launcher.getAllAppsController();
         float scrollRange = Math.max(controller.getShiftRange(), 1);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
index 0c49e5f..0f02e26 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
@@ -18,8 +18,6 @@
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
 
 import android.content.Context;
-import android.graphics.Point;
-import android.graphics.Rect;
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.Launcher;
@@ -71,13 +69,6 @@
     }
 
     public static float[] getOverviewScaleAndOffsetForModalState(BaseDraggingActivity activity) {
-        Point taskSize = activity.<RecentsView>getOverviewPanel().getSelectedTaskSize();
-        Rect modalTaskSize = new Rect();
-        activity.<RecentsView>getOverviewPanel().getModalTaskSize(modalTaskSize);
-
-        float scale = Math.min((float) modalTaskSize.height() / taskSize.y,
-                (float) modalTaskSize.width() / taskSize.x);
-
-        return new float[] {scale, NO_OFFSET};
+        return new float[] {activity.getDeviceProfile().overviewModalTaskScale, NO_OFFSET};
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 6427e09..c0810b9 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -65,12 +65,9 @@
 
     @Override
     public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
-        RecentsView recentsView = launcher.getOverviewPanel();
-        float workspacePageHeight = launcher.getDeviceProfile().getCellLayoutHeight();
-        recentsView.getTaskSize(sTempRect);
-        float scale = (float) sTempRect.height() / workspacePageHeight;
         float parallaxFactor = 0.5f;
-        return new ScaleAndTranslation(scale, 0, -getDefaultSwipeHeight(launcher) * parallaxFactor);
+        return new ScaleAndTranslation(launcher.getDeviceProfile().overviewTaskWorkspaceScale, 0,
+                -getDefaultSwipeHeight(launcher) * parallaxFactor);
     }
 
     @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..4d2f965 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -42,17 +42,10 @@
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_SCRIM_FADE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_SCALE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
-import static com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController.ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD;
-import static com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController.ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD;
-import static com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController.ALL_APPS_SCRIM_OPAQUE_THRESHOLD;
-import static com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController.ALL_APPS_SCRIM_VISIBLE_THRESHOLD;
 import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
-import static com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE;
-import static com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE;
 
 import android.animation.ValueAnimator;
 
@@ -60,8 +53,8 @@
 import com.android.launcher3.Hotseat;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.Workspace;
-import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.touch.AllAppsSwipeController;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.util.DisplayController;
 import com.android.quickstep.util.RecentsAtomicAnimationFactory;
@@ -182,23 +175,9 @@
             }
             config.duration = Math.max(config.duration, mHintToNormalDuration);
         } else if (fromState == ALL_APPS && toState == NORMAL) {
-            boolean isTablet = mActivity.getDeviceProfile().isTablet;
-            config.setInterpolator(ANIM_ALL_APPS_FADE,
-                    isTablet ? FINAL_FRAME : Interpolators.clampToProgress(LINEAR,
-                            1 - ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD,
-                            1 - ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD));
-            config.setInterpolator(ANIM_SCRIM_FADE, Interpolators.clampToProgress(LINEAR,
-                    1 - ALL_APPS_SCRIM_OPAQUE_THRESHOLD,
-                    1 - ALL_APPS_SCRIM_VISIBLE_THRESHOLD));
-            config.setInterpolator(ANIM_VERTICAL_PROGRESS, EMPHASIZED_ACCELERATE);
-            if (!isTablet) {
-                config.setInterpolator(ANIM_WORKSPACE_FADE, INSTANT);
-            }
+            AllAppsSwipeController.applyAllAppsToNormalConfig(mActivity, config);
         } else if (fromState == NORMAL && toState == ALL_APPS) {
-            if (mActivity.getDeviceProfile().isTablet) {
-                config.setInterpolator(ANIM_VERTICAL_PROGRESS, EMPHASIZED_DECELERATE);
-            }
-            // TODO(b/231682175): centralize this setup in AllAppsSwipeController
+            AllAppsSwipeController.applyNormalToAllAppsAnimConfig(mActivity, config);
         }
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 53dc9dd..d06f64a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -120,8 +120,8 @@
         mSwipeDetector = new BothAxesSwipeDetector(mLauncher, this);
         mRecentsView = mLauncher.getOverviewPanel();
         mXRange = mLauncher.getDeviceProfile().widthPx / 2f;
-        mYRange = LayoutUtils.getShelfTrackingDistance(
-            mLauncher, mLauncher.getDeviceProfile(), mRecentsView.getPagedOrientationHandler());
+        mYRange = LayoutUtils.getShelfTrackingDistance(mLauncher.getDeviceProfile(),
+                mRecentsView.getPagedOrientationHandler());
         mMaxYProgress = mLauncher.getDeviceProfile().heightPx / mYRange;
         mMotionPauseDetector = new MotionPauseDetector(mLauncher);
         mMotionPauseMinDisplacement = mLauncher.getResources().getDimension(
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index e56c90c..7394945 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;
     }
@@ -254,8 +144,8 @@
                     .createPlaybackController();
             mLauncher.getStateManager().setCurrentUserControlledAnimation(mCurrentAnimation);
             RecentsView recentsView = mLauncher.getOverviewPanel();
-            totalShift = LayoutUtils.getShelfTrackingDistance(mLauncher,
-                    mLauncher.getDeviceProfile(), recentsView.getPagedOrientationHandler());
+            totalShift = LayoutUtils.getShelfTrackingDistance(mLauncher.getDeviceProfile(),
+                    recentsView.getPagedOrientationHandler());
         } else {
             mCurrentAnimation = mLauncher.getStateManager()
                     .createAnimationToNewWorkspace(mToState, config);
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 76f7718..045530c 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -58,6 +58,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;
@@ -140,7 +141,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 +172,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 +301,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));
@@ -315,7 +321,7 @@
     }
 
     private void initStateCallbacks() {
-        mStateCallback = new MultiStateCallback(STATE_NAMES);
+        mStateCallback = new MultiStateCallback(STATE_NAMES.toArray(new String[0]));
 
         mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED,
                 this::onLauncherPresentAndGestureStarted);
@@ -442,7 +448,7 @@
             });
 
         setupRecentsViewUi();
-        linkRecentsViewScroll();
+        mRecentsView.runOnPageScrollsInitialized(this::linkRecentsViewScroll);
         activity.runOnBindToTouchInteractionService(this::onLauncherBindToService);
 
         mActivity.registerActivityLifecycleCallbacks(mLifecycleCallbacks);
@@ -571,7 +577,7 @@
 
     protected void notifyGestureAnimationStartToRecents() {
         Task[] runningTasks;
-        if (mIsSwipeForStagedSplit) {
+        if (mIsSwipeForSplit) {
             int[] splitTaskIds = TopTaskTracker.INSTANCE.get(mContext).getRunningSplitTaskIds();
             runningTasks = mGestureState.getRunningTask().getPlaceholderTasks(splitTaskIds);
         } else {
@@ -862,6 +868,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 +884,7 @@
                     InteractionJankMonitorWrapper.begin(mRecentsView,
                             InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME);
 
-                    mRecentsView.post(() ->
-                            mRecentsView.getViewTreeObserver().removeOnDrawListener(this));
+                    rv.post(() -> rv.getViewTreeObserver().removeOnDrawListener(this));
                 }
             });
         }
@@ -1137,7 +1143,9 @@
             mInputConsumerProxy.enable();
         }
         if (endTarget == HOME) {
-            duration = HOME_DURATION;
+            duration = mActivity != null && mActivity.getDeviceProfile().isTaskbarPresent
+                    ? StaggeredWorkspaceAnim.DURATION_TASKBAR_MS
+                    : StaggeredWorkspaceAnim.DURATION_MS;
             // Early detach the nav bar once the endTarget is determined as HOME
             if (mRecentsAnimationController != null) {
                 mRecentsAnimationController.detachNavigationBarFromApp(true);
@@ -1280,15 +1288,18 @@
                     ? runningTaskTarget.taskInfo.launchCookies
                     : new ArrayList<>();
             boolean isTranslucent = runningTaskTarget != null && runningTaskTarget.isTranslucent;
+            boolean hasValidLeash = runningTaskTarget != null
+                    && runningTaskTarget.leash != null
+                    && runningTaskTarget.leash.isValid();
             boolean appCanEnterPip = !mDeviceState.isPipActive()
-                    && runningTaskTarget != null
+                    && hasValidLeash
                     && runningTaskTarget.allowEnterPip
                     && runningTaskTarget.taskInfo.pictureInPictureParams != null
                     && runningTaskTarget.taskInfo.pictureInPictureParams.isAutoEnterEnabled();
             HomeAnimationFactory homeAnimFactory =
                     createHomeAnimationFactory(cookies, duration, isTranslucent, appCanEnterPip,
                             runningTaskTarget);
-            mIsSwipingPipToHome = !mIsSwipeForStagedSplit && appCanEnterPip;
+            mIsSwipingPipToHome = !mIsSwipeForSplit && appCanEnterPip;
             final RectFSpringAnim[] windowAnim;
             if (mIsSwipingPipToHome) {
                 mSwipePipToHomeAnimator = createWindowAnimationToPip(
@@ -1390,9 +1401,6 @@
         }
     }
 
-    /**
-     * TODO(b/195473090) handle multiple task simulators (if needed) for PIP
-     */
     private SwipePipToHomeAnimator createWindowAnimationToPip(HomeAnimationFactory homeAnimFactory,
             RemoteAnimationTargetCompat runningTaskTarget, float startProgress) {
         // Directly animate the app to PiP (picture-in-picture) mode
@@ -1417,6 +1425,16 @@
                         runningTaskTarget.taskInfo.pictureInPictureParams,
                         homeRotation,
                         mDp.hotseatBarSizePx);
+        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 +1442,7 @@
                 .setLeash(runningTaskTarget.leash)
                 .setSourceRectHint(
                         runningTaskTarget.taskInfo.pictureInPictureParams.getSourceRectHint())
-                .setAppBounds(taskInfo.configuration.windowConfiguration.getBounds())
+                .setAppBounds(appBounds)
                 .setHomeToWindowPositionMap(homeToWindowPositionMap)
                 .setStartBounds(startRect)
                 .setDestinationBounds(destinationBounds)
@@ -1611,7 +1629,8 @@
         // Cleanup when switching handlers
         mInputConsumerProxy.unregisterCallback();
         mActivityInitListener.unregister();
-        ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mActivityRestartListener);
+        TaskStackChangeListeners.getInstance().unregisterTaskStackListener(
+                mActivityRestartListener);
         mTaskSnapshot = null;
     }
 
@@ -1675,7 +1694,7 @@
         boolean wasVisible = mWasLauncherAlreadyVisible || mGestureStarted;
         mActivityInterface.onTransitionCancelled(wasVisible, mGestureState.getEndTarget());
 
-        if (mRecentsAnimationTargets != null) {
+        if (mRecentsAnimationTargets != null && wasVisible) {
             setDividerShown(true /* shown */, true /* immediate */);
         }
 
@@ -1792,12 +1811,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,
@@ -2029,8 +2049,8 @@
 
         float scrollOffset = Math.abs(mRecentsView.getScrollOffset(mRecentsView.getCurrentPage()));
         int maxScrollOffset = mRecentsView.getPagedOrientationHandler().getPrimaryValue(
-                mRecentsView.getLastComputedTaskSize().width(),
-                mRecentsView.getLastComputedTaskSize().height());
+                mActivity.getDeviceProfile().overviewTaskRect.width(),
+                mActivity.getDeviceProfile().overviewTaskRect.height());
         maxScrollOffset += mRecentsView.getPageSpacing();
 
         float maxScaleProgress =
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..a922f58 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -33,12 +33,9 @@
 import android.animation.ObjectAnimator;
 import android.annotation.TargetApi;
 import android.content.Context;
-import android.content.res.Resources;
 import android.graphics.Color;
-import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Build;
-import android.view.Gravity;
 import android.view.MotionEvent;
 import android.view.View;
 
@@ -46,7 +43,6 @@
 import androidx.annotation.UiThread;
 
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.R;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.statehandlers.DepthController;
@@ -56,13 +52,10 @@
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.NavigationMode;
-import com.android.launcher3.util.WindowBounds;
 import com.android.launcher3.views.ScrimView;
 import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
-import com.android.quickstep.util.SplitScreenBounds;
 import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
@@ -112,8 +105,8 @@
         activity.getStateManager().goToState(startState, activityVisible);
     }
 
-    public abstract int getSwipeUpDestinationAndLength(
-            DeviceProfile dp, Context context, Rect outRect,
+    /** Gets swipe-up destination and length. */
+    public abstract int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context,
             PagedOrientationHandler orientationHandler);
 
     /** Called when the animation to home has fully settled. */
@@ -218,156 +211,6 @@
     }
 
     /**
-     * Calculates the taskView size for the provided device configuration.
-     */
-    public final void calculateTaskSize(Context context, DeviceProfile dp, Rect outRect) {
-        Resources res = context.getResources();
-        float maxScale = res.getFloat(R.dimen.overview_max_scale);
-        if (dp.isTablet) {
-            Rect gridRect = new Rect();
-            calculateGridSize(dp, gridRect);
-
-            calculateTaskSizeInternal(context, dp, gridRect, maxScale, Gravity.CENTER, outRect);
-        } else {
-            int taskMargin = dp.overviewTaskMarginPx;
-            calculateTaskSizeInternal(context, dp,
-                    dp.overviewTaskThumbnailTopMarginPx,
-                    dp.getOverviewActionsClaimedSpace(),
-                    res.getDimensionPixelSize(R.dimen.overview_minimum_next_prev_size) + taskMargin,
-                    maxScale,
-                    Gravity.CENTER,
-                    outRect);
-        }
-    }
-
-    private void calculateTaskSizeInternal(Context context, DeviceProfile dp, int claimedSpaceAbove,
-            int claimedSpaceBelow, int minimumHorizontalPadding, float maxScale, int gravity,
-            Rect outRect) {
-        Rect insets = dp.getInsets();
-
-        Rect potentialTaskRect = new Rect(0, 0, dp.widthPx, dp.heightPx);
-        potentialTaskRect.inset(insets.left, insets.top, insets.right, insets.bottom);
-        potentialTaskRect.inset(
-                minimumHorizontalPadding,
-                claimedSpaceAbove,
-                minimumHorizontalPadding,
-                claimedSpaceBelow);
-
-        calculateTaskSizeInternal(context, dp, potentialTaskRect, maxScale, gravity, outRect);
-    }
-
-    private void calculateTaskSizeInternal(Context context, DeviceProfile dp,
-            Rect potentialTaskRect, float maxScale, int gravity, Rect outRect) {
-        PointF taskDimension = getTaskDimension(context, dp);
-
-        float scale = Math.min(
-                potentialTaskRect.width() / taskDimension.x,
-                potentialTaskRect.height() / taskDimension.y);
-        scale = Math.min(scale, maxScale);
-        int outWidth = Math.round(scale * taskDimension.x);
-        int outHeight = Math.round(scale * taskDimension.y);
-
-        Gravity.apply(gravity, outWidth, outHeight, potentialTaskRect, outRect);
-    }
-
-    private static PointF getTaskDimension(Context context, DeviceProfile dp) {
-        PointF dimension = new PointF();
-        getTaskDimension(context, 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);
-            }
-        }
-    }
-
-    /**
-     * Calculates the overview grid size for the provided device configuration.
-     */
-    public final void calculateGridSize(DeviceProfile dp, Rect outRect) {
-        Rect insets = dp.getInsets();
-        int topMargin = dp.overviewTaskThumbnailTopMarginPx;
-        int bottomMargin = dp.getOverviewActionsClaimedSpace();
-        int sideMargin = dp.overviewGridSideMargin;
-
-        outRect.set(0, 0, dp.widthPx, dp.heightPx);
-        outRect.inset(Math.max(insets.left, sideMargin), insets.top + topMargin,
-                Math.max(insets.right, sideMargin), Math.max(insets.bottom, bottomMargin));
-    }
-
-    /**
-     * Calculates the overview grid non-focused task size for the provided device configuration.
-     */
-    public final void calculateGridTaskSize(Context context, DeviceProfile dp, Rect outRect,
-            PagedOrientationHandler orientedState) {
-        Resources res = context.getResources();
-        Rect taskRect = new Rect();
-        calculateTaskSize(context, dp, taskRect);
-
-        float rowHeight =
-                (taskRect.height() + dp.overviewTaskThumbnailTopMarginPx - dp.overviewRowSpacing)
-                        / 2f;
-
-        PointF taskDimension = getTaskDimension(context, dp);
-        float scale = (rowHeight - dp.overviewTaskThumbnailTopMarginPx) / taskDimension.y;
-        int outWidth = Math.round(scale * taskDimension.x);
-        int outHeight = Math.round(scale * taskDimension.y);
-
-        int gravity = Gravity.TOP;
-        gravity |= orientedState.getRecentsRtlSetting(res) ? Gravity.RIGHT : Gravity.LEFT;
-        Gravity.apply(gravity, outWidth, outHeight, taskRect, outRect);
-    }
-
-    /**
-     * Calculates the modal taskView size for the provided device configuration
-     */
-    public final void calculateModalTaskSize(Context context, DeviceProfile dp, Rect outRect) {
-        calculateTaskSize(context, dp, outRect);
-        float maxScale = context.getResources().getFloat(R.dimen.overview_modal_max_scale);
-        calculateTaskSizeInternal(
-                context, dp,
-                dp.overviewTaskMarginPx,
-                dp.heightPx - outRect.bottom - dp.getInsets().bottom,
-                Math.round((dp.availableWidthPx - outRect.width() * maxScale) / 2),
-                1f /*maxScale*/,
-                Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM,
-                outRect);
-    }
-
-    /**
      * Called when the gesture ends and the animation starts towards the given target. Used to add
      * an optional additional animation with the same duration.
      */
diff --git a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
index ba61574..2ca5a2e 100644
--- a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
@@ -60,13 +60,13 @@
 
     /** 2 */
     @Override
-    public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect,
+    public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context,
             PagedOrientationHandler orientationHandler) {
-        calculateTaskSize(context, dp, outRect);
         if (dp.isVerticalBarLayout() && DisplayController.getNavigationMode(context) != NO_BUTTON) {
-            return dp.isSeascape() ? outRect.left : (dp.widthPx - outRect.right);
+            return dp.isSeascape()
+                    ? dp.overviewTaskRect.left : (dp.widthPx - dp.overviewTaskRect.right);
         } else {
-            return dp.heightPx - outRect.bottom;
+            return dp.heightPx - dp.overviewTaskRect.bottom;
         }
     }
 
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..acdbbbd 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -38,6 +38,7 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -82,11 +83,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 +98,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;
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..d3065bd 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -70,14 +70,14 @@
     }
 
     @Override
-    public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect,
+    public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context,
             PagedOrientationHandler orientationHandler) {
-        calculateTaskSize(context, dp, outRect);
         if (dp.isVerticalBarLayout()
                 && DisplayController.getNavigationMode(context) != NavigationMode.NO_BUTTON) {
-            return dp.isSeascape() ? outRect.left : (dp.widthPx - outRect.right);
+            return dp.isSeascape()
+                    ? dp.overviewTaskRect.left : (dp.widthPx - dp.overviewTaskRect.right);
         } else {
-            return LayoutUtils.getShelfTrackingDistance(context, dp, orientationHandler);
+            return LayoutUtils.getShelfTrackingDistance(dp, orientationHandler);
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index 50d1244..196a664 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -91,7 +91,7 @@
             mActivity.setHintUserWillBeActive();
         }
 
-        if (!canUseWorkspaceView || appCanEnterPip || mIsSwipeForStagedSplit) {
+        if (!canUseWorkspaceView || appCanEnterPip || mIsSwipeForSplit) {
             return new LauncherHomeAnimationFactory();
         }
         if (workspaceView instanceof LauncherAppWidgetHostView) {
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index dffdc5a..1e7e89e 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -235,6 +235,10 @@
             }
         };
 
+        RecentsView<?, ?> visibleRecentsView = activityInterface.getVisibleRecentsView();
+        if (visibleRecentsView != null) {
+            visibleRecentsView.moveFocusedTaskToFront();
+        }
         if (mTaskAnimationManager.isRecentsAnimationRunning()) {
             cmd.mActiveCallbacks = mTaskAnimationManager.continueRecentsAnimation(gestureState);
             cmd.mActiveCallbacks.addListener(interactionHandler);
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index 0efe666..9e3173c 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -20,11 +20,11 @@
 import static android.content.Intent.ACTION_PACKAGE_CHANGED;
 import static android.content.Intent.ACTION_PACKAGE_REMOVED;
 
-import static com.android.launcher3.Utilities.createHomeIntent;
 import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY;
 import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
 import static com.android.systemui.shared.system.PackageManagerWrapper.ACTION_PREFERRED_ACTIVITY_CHANGED;
 
+import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -33,8 +33,12 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.os.Bundle;
 import android.util.SparseIntArray;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.tracing.OverviewComponentObserverProto;
 import com.android.launcher3.tracing.TouchInteractionServiceProto;
 import com.android.launcher3.util.SimpleBroadcastReceiver;
@@ -276,4 +280,34 @@
         overviewComponentObserver.setOverviewActivityResumed(mActivityInterface.isResumed());
         serviceProto.setOverviewComponentObvserver(overviewComponentObserver);
     }
+
+    /**
+     * Starts the intent for the current home activity.
+     */
+    public static void startHomeIntentSafely(@NonNull Context context, @Nullable Bundle options) {
+        RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState(context);
+        OverviewComponentObserver observer = new OverviewComponentObserver(context, deviceState);
+        Intent intent = observer.getHomeIntent();
+        observer.onDestroy();
+        deviceState.destroy();
+        startHomeIntentSafely(context, intent, options);
+    }
+
+    /**
+     * Starts the intent for the current home activity.
+     */
+    public static void startHomeIntentSafely(
+            @NonNull Context context, @NonNull Intent homeIntent, @Nullable Bundle options) {
+        try {
+            context.startActivity(homeIntent, options);
+        } catch (NullPointerException | ActivityNotFoundException | SecurityException e) {
+            context.startActivity(createHomeIntent(), options);
+        }
+    }
+
+    private static Intent createHomeIntent() {
+        return new Intent(Intent.ACTION_MAIN)
+                .addCategory(Intent.CATEGORY_HOME)
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 528fb97..d216a6a 100644
--- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -2,7 +2,6 @@
 
 import android.app.Activity;
 import android.content.Context;
-import android.graphics.Rect;
 import android.os.Bundle;
 
 import androidx.annotation.Nullable;
@@ -32,8 +31,7 @@
             }
 
             case TestProtocol.REQUEST_BACKGROUND_TO_OVERVIEW_SWIPE_HEIGHT: {
-                final float swipeHeight =
-                        LayoutUtils.getShelfTrackingDistance(mContext, mDeviceProfile,
+                final float swipeHeight = LayoutUtils.getShelfTrackingDistance(mDeviceProfile,
                                 PagedOrientationHandler.PORTRAIT);
                 response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight);
                 return response;
@@ -43,10 +41,8 @@
                 if (!mDeviceProfile.isTablet) {
                     return null;
                 }
-                Rect focusedTaskRect = new Rect();
-                LauncherActivityInterface.INSTANCE.calculateTaskSize(mContext, mDeviceProfile,
-                        focusedTaskRect);
-                response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, focusedTaskRect.height());
+                response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
+                        mDeviceProfile.overviewTaskRect.height());
                 return response;
             }
 
@@ -54,10 +50,10 @@
                 if (!mDeviceProfile.isTablet) {
                     return null;
                 }
-                Rect gridTaskRect = new Rect();
-                LauncherActivityInterface.INSTANCE.calculateGridTaskSize(mContext, mDeviceProfile,
-                        gridTaskRect, PagedOrientationHandler.PORTRAIT);
-                response.putParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD, gridTaskRect);
+                boolean isRecentsRtl = PagedOrientationHandler.PORTRAIT.getRecentsRtlSetting(
+                        mContext.getResources());
+                response.putParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD,
+                        mDeviceProfile.getOverviewGridTaskRect(isRecentsRtl));
                 return response;
             }
 
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 097850f..cd93dbe 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -34,7 +34,7 @@
 import com.android.systemui.shared.system.KeyguardManagerCompat;
 import com.android.wm.shell.recents.IRecentTasksListener;
 import com.android.wm.shell.util.GroupedRecentTaskInfo;
-import com.android.wm.shell.util.StagedSplitBounds;
+import com.android.wm.shell.util.SplitBounds;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -195,19 +195,19 @@
                                 tmpLockedUsers.get(task2Key.userId) /* isLocked */);
                 task2.setLastSnapshotData(taskInfo2);
             }
-            final SplitConfigurationOptions.StagedSplitBounds launcherSplitBounds =
-                    convertSplitBounds(rawTask.mStagedSplitBounds);
+            final SplitConfigurationOptions.SplitBounds launcherSplitBounds =
+                    convertSplitBounds(rawTask.mSplitBounds);
             allTasks.add(new GroupTask(task1, task2, launcherSplitBounds));
         }
 
         return allTasks;
     }
 
-    private SplitConfigurationOptions.StagedSplitBounds convertSplitBounds(
-            StagedSplitBounds shellSplitBounds) {
+    private SplitConfigurationOptions.SplitBounds convertSplitBounds(
+            SplitBounds shellSplitBounds) {
         return shellSplitBounds == null ?
                 null :
-                new SplitConfigurationOptions.StagedSplitBounds(
+                new SplitConfigurationOptions.SplitBounds(
                         shellSplitBounds.leftTopBounds, shellSplitBounds.rightBottomBounds,
                         shellSplitBounds.leftTopTaskId, shellSplitBounds.rightBottomTaskId);
     }
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 4f0b976..67ce606 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -21,11 +21,11 @@
 import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION;
 import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_DURATION;
 import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_PRE_DELAY;
-import static com.android.launcher3.Utilities.createHomeIntent;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.graphics.SysUiScrim.SYSUI_PROGRESS;
 import static com.android.launcher3.testing.TestProtocol.BAD_STATE;
 import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
+import static com.android.quickstep.OverviewComponentObserver.startHomeIntentSafely;
 import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
 import static com.android.quickstep.TaskViewUtils.createRecentsWindowAnimator;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
@@ -428,7 +428,7 @@
         RemoteAnimationAdapterCompat adapterCompat =
                 new RemoteAnimationAdapterCompat(runner, HOME_APPEAR_DURATION, 0,
                         getIApplicationThread());
-        startActivity(createHomeIntent(),
+        startHomeIntentSafely(this,
                 ActivityOptionsCompat.makeRemoteAnimation(adapterCompat).toBundle());
     }
 
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 2007ee1..fefef2f 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -104,8 +104,6 @@
         }
         if (mSplitScreenMinimized != splitScreenMinimized) {
             mSplitScreenMinimized = splitScreenMinimized;
-            UI_HELPER_EXECUTOR.execute(() -> SystemUiProxy.INSTANCE.get(context)
-                    .setSplitScreenMinimized(splitScreenMinimized));
         }
     }
 
@@ -222,7 +220,6 @@
      */
     public void enableInputConsumer() {
         UI_HELPER_EXECUTOR.submit(() -> {
-            mController.hideCurrentInputMethod();
             mController.setInputConsumerEnabled(true);
         });
     }
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 1634c08..442578e 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -36,6 +36,7 @@
 import com.android.launcher3.util.Executors.SimpleThreadFactory;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.TaskVisualsChangeListener;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -54,7 +55,8 @@
  * Singleton class to load and manage recents model.
  */
 @TargetApi(Build.VERSION_CODES.O)
-public class RecentsModel implements IconChangeListener, TaskStackChangeListener {
+public class RecentsModel implements IconChangeListener, TaskStackChangeListener,
+        TaskVisualsChangeListener {
 
     // We do not need any synchronization for this variable as its only written on UI thread.
     public static final MainThreadInitializedObject<RecentsModel> INSTANCE =
@@ -77,6 +79,7 @@
 
         IconProvider iconProvider = new IconProvider(context);
         mIconCache = new TaskIconCache(context, RECENTS_MODEL_EXECUTOR, iconProvider);
+        mIconCache.registerTaskVisualsChangeListener(this);
         mThumbnailCache = new TaskThumbnailCache(context, RECENTS_MODEL_EXECUTOR);
 
         TaskStackChangeListeners.getInstance().registerTaskStackListener(this);
@@ -204,6 +207,13 @@
     }
 
     @Override
+    public void onTaskIconChanged(int taskId) {
+        for (TaskVisualsChangeListener listener : mThumbnailChangeListeners) {
+            listener.onTaskIconChanged(taskId);
+        }
+    }
+
+    @Override
     public void onSystemIconStateChanged(String iconState) {
         mIconCache.clearCache();
     }
@@ -226,20 +236,4 @@
         writer.println(prefix + "RecentsModel:");
         mTaskList.dump("  ", writer);
     }
-
-    /**
-     * Listener for receiving various task properties changes
-     */
-    public interface TaskVisualsChangeListener {
-
-        /**
-         * Called whn the task thumbnail changes
-         */
-        Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData);
-
-        /**
-         * Called when the icon for a task changes
-         */
-        void onTaskIconChanged(String pkg, UserHandle user);
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
index b20d488..1bd808d 100644
--- a/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
+++ b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
@@ -17,8 +17,6 @@
 
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 
-import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
-
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
 import java.util.ArrayList;
@@ -114,10 +112,6 @@
     }
 
     public void release() {
-        if (ENABLE_SHELL_TRANSITIONS) {
-            mReleaseChecks.clear();
-            return;
-        }
         if (mReleased) {
             return;
         }
diff --git a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
index c3ea256..7183c49 100644
--- a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
+++ b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
@@ -20,7 +20,7 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.util.TaskViewSimulator;
 import com.android.quickstep.util.TransformParams;
@@ -34,7 +34,7 @@
  */
 public class RemoteTargetGluer {
     private RemoteTargetHandle[] mRemoteTargetHandles;
-    private StagedSplitBounds mStagedSplitBounds;
+    private SplitBounds mSplitBounds;
 
     /**
      * Use this constructor if remote targets are split-screen independent
@@ -118,18 +118,18 @@
 
             // remoteTargetHandle[0] denotes topLeft task, so we pass in the bottomRight to exclude,
             // vice versa
-            mStagedSplitBounds = new StagedSplitBounds(
+            mSplitBounds = new SplitBounds(
                     topLeftTarget.startScreenSpaceBounds,
                     bottomRightTarget.startScreenSpaceBounds, splitIds[0], splitIds[1]);
             mRemoteTargetHandles[0].mTransformParams.setTargetSet(
                     createRemoteAnimationTargetsForTarget(targets, bottomRightTarget));
             mRemoteTargetHandles[0].mTaskViewSimulator.setPreview(topLeftTarget,
-                    mStagedSplitBounds);
+                    mSplitBounds);
 
             mRemoteTargetHandles[1].mTransformParams.setTargetSet(
                     createRemoteAnimationTargetsForTarget(targets, topLeftTarget));
             mRemoteTargetHandles[1].mTaskViewSimulator.setPreview(bottomRightTarget,
-                    mStagedSplitBounds);
+                    mSplitBounds);
         }
         return mRemoteTargetHandles;
     }
@@ -173,8 +173,8 @@
         return mRemoteTargetHandles;
     }
 
-    public StagedSplitBounds getStagedSplitBounds() {
-        return mStagedSplitBounds;
+    public SplitBounds getSplitBounds() {
+        return mSplitBounds;
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index 9667108..3c15d0f 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;
@@ -50,7 +49,6 @@
 public abstract class SwipeUpAnimationLogic implements
         RecentsAnimationCallbacks.RecentsAnimationListener{
 
-    protected static final Rect TEMP_RECT = new Rect();
     protected final RemoteTargetGluer mTargetGluer;
 
     protected DeviceProfile mDp;
@@ -72,16 +70,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();
@@ -95,7 +91,7 @@
     protected void initTransitionEndpoints(DeviceProfile dp) {
         mDp = dp;
         mTransitionDragLength = mGestureState.getActivityInterface().getSwipeUpDestinationAndLength(
-                dp, mContext, TEMP_RECT, mRemoteTargetHandles[0].getTaskViewSimulator()
+                dp, mContext, mRemoteTargetHandles[0].getTaskViewSimulator()
                         .getOrientationState().getOrientationHandler());
         mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength;
 
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 39d8b54..9a2619b 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -270,18 +270,6 @@
         }
     }
 
-    @Override
-    public Rect getNonMinimizedSplitScreenSecondaryBounds() {
-        if (mSystemUiProxy != null) {
-            try {
-                return mSystemUiProxy.getNonMinimizedSplitScreenSecondaryBounds();
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call getNonMinimizedSplitScreenSecondaryBounds", e);
-            }
-        }
-        return null;
-    }
-
     public float getLastNavButtonAlpha() {
         return mLastNavButtonAlpha;
     }
@@ -385,28 +373,6 @@
     }
 
     @Override
-    public void handleImageAsScreenshot(Bitmap bitmap, Rect rect, Insets insets, int i) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.handleImageAsScreenshot(bitmap, rect, insets, i);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call handleImageAsScreenshot", e);
-            }
-        }
-    }
-
-    @Override
-    public void setSplitScreenMinimized(boolean minimized) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.setSplitScreenMinimized(minimized);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call setSplitScreenMinimized", e);
-            }
-        }
-    }
-
-    @Override
     public void notifySwipeUpGestureStarted() {
         if (mSystemUiProxy != null) {
             try {
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 54f457d..fd7e367 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -195,7 +195,10 @@
                 } else if (nonAppTargets != null && nonAppTargets.length > 0) {
                     TaskViewUtils.createSplitAuxiliarySurfacesAnimator(
                             RemoteAnimationTargetCompat.wrap(nonAppTargets) /* nonApps */,
-                            true /*shown*/, dividerAnimator -> dividerAnimator.start());
+                            true /*shown*/, dividerAnimator -> {
+                                dividerAnimator.start();
+                                dividerAnimator.end();
+                            });
                 }
                 if (mController != null) {
                     if (mLastAppearedTaskTarget == null
diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java
index 3803f03..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..092854f 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -16,11 +16,8 @@
 
 package com.android.quickstep;
 
-import static android.view.Display.DEFAULT_DISPLAY;
-
 import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_SELECTIONS;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_SPLIT_SCREEN_TAP;
 
 import android.app.Activity;
 import android.app.ActivityOptions;
@@ -32,6 +29,8 @@
 import android.view.View;
 import android.window.SplashScreen;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
@@ -39,6 +38,7 @@
 import com.android.launcher3.model.WellbeingModel;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.popup.SystemShortcut.AppInfo;
+import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.util.InstantAppResolver;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
 import com.android.quickstep.views.RecentsView;
@@ -55,21 +55,35 @@
 
 import java.util.Collections;
 import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 
 /**
  * Represents a system shortcut that can be shown for a recent task.
  */
 public interface TaskShortcutFactory {
-    SystemShortcut getShortcut(BaseDraggingActivity activity,
-            TaskIdAttributeContainer taskContainer);
+    @Nullable
+    default List<SystemShortcut> getShortcuts(BaseDraggingActivity activity,
+            TaskIdAttributeContainer taskContainer) {
+        return null;
+    }
 
     default boolean showForSplitscreen() {
         return false;
     }
 
+    /** @return a singleton list if the provided shortcut is non-null, null otherwise */
+    @Nullable
+    default List<SystemShortcut> createSingletonShortcutList(@Nullable SystemShortcut shortcut) {
+        if (shortcut != null) {
+            return Collections.singletonList(shortcut);
+        }
+        return null;
+    }
+
     TaskShortcutFactory APP_INFO = new TaskShortcutFactory() {
         @Override
-        public SystemShortcut getShortcut(BaseDraggingActivity activity,
+        public List<SystemShortcut> getShortcuts(BaseDraggingActivity activity,
                 TaskIdAttributeContainer taskContainer) {
             TaskView taskView = taskContainer.getTaskView();
             AppInfo.SplitAccessibilityInfo accessibilityInfo =
@@ -77,7 +91,8 @@
                             TaskUtils.getTitle(taskView.getContext(), taskContainer.getTask()),
                             taskContainer.getA11yNodeId()
                     );
-            return new AppInfo(activity, taskContainer.getItemInfo(), taskView, accessibilityInfo);
+            return Collections.singletonList(new AppInfo(activity, taskContainer.getItemInfo(),
+                    taskView, accessibilityInfo));
         }
 
         @Override
@@ -103,7 +118,7 @@
         protected abstract boolean onActivityStarted(BaseDraggingActivity activity);
 
         @Override
-        public SystemShortcut getShortcut(BaseDraggingActivity activity,
+        public List<SystemShortcut> getShortcuts(BaseDraggingActivity activity,
                 TaskIdAttributeContainer taskContainer) {
             final Task task  = taskContainer.getTask();
             if (!task.isDockable) {
@@ -112,8 +127,8 @@
             if (!isAvailable(activity, task.key.displayId)) {
                 return null;
             }
-            return new MultiWindowSystemShortcut(mIconRes, mTextRes, activity, taskContainer, this,
-                    mLauncherEvent);
+            return Collections.singletonList(new MultiWindowSystemShortcut(mIconRes,
+                    mTextRes, activity, taskContainer, this, mLauncherEvent));
         }
     }
 
@@ -242,34 +257,50 @@
         }
     }
 
-    /** @Deprecated */
-    TaskShortcutFactory SPLIT_SCREEN = new MultiWindowFactory(R.drawable.ic_split_screen,
-            R.string.recent_task_option_split_screen, LAUNCHER_SYSTEM_SHORTCUT_SPLIT_SCREEN_TAP) {
-
+    /**
+     * Does NOT add split options in the following scenarios:
+     * * The taskView to add split options is already showing split screen tasks
+     * * There aren't at least 2 tasks in overview to show split options for
+     * * Split isn't supported by the task itself (non resizable activity)
+     * * We aren't currently in multi-window
+     * * The taskView to show split options for is the focused task AND we haven't started
+     * scrolling in overview (if we haven't scrolled, there's a split overview action button so
+     * we don't need this menu option)
+     */
+    TaskShortcutFactory SPLIT_SELECT = new TaskShortcutFactory() {
         @Override
-        protected boolean isAvailable(BaseDraggingActivity activity, int displayId) {
-            // Don't show menu-item if already in multi-window and the task is from
-            // the secondary display.
-            // TODO(b/118266305): Temporarily disable splitscreen for secondary display while new
-            // implementation is enabled
-            return !activity.getDeviceProfile().isMultiWindowMode
-                    && (displayId == -1 || displayId == DEFAULT_DISPLAY);
-        }
+        public List<SystemShortcut> getShortcuts(BaseDraggingActivity activity,
+                TaskIdAttributeContainer taskContainer) {
+            DeviceProfile deviceProfile = activity.getDeviceProfile();
+            final Task task  = taskContainer.getTask();
+            final TaskView taskView = taskContainer.getTaskView();
+            final RecentsView recentsView = taskView.getRecentsView();
+            final PagedOrientationHandler orientationHandler =
+                    recentsView.getPagedOrientationHandler();
 
-        @Override
-        protected ActivityOptions makeLaunchOptions(Activity activity) {
-            final int navBarPosition = WindowManagerWrapper.getInstance().getNavBarPosition(
-                    activity.getDisplayId());
-            if (navBarPosition == WindowManagerWrapper.NAV_BAR_POS_INVALID) {
+            int[] taskViewTaskIds = taskView.getTaskIds();
+            boolean taskViewHasMultipleTasks = taskViewTaskIds[0] != -1 &&
+                    taskViewTaskIds[1] != -1;
+            boolean notEnoughTasksToSplit = recentsView.getTaskViewCount() < 2;
+            boolean isFocusedTask = deviceProfile.isTablet && taskView.isFocusedTask();
+            boolean isTaskInExpectedScrollPosition =
+                    recentsView.isTaskInExpectedScrollPosition(recentsView.indexOfChild(taskView));
+            boolean isTaskSplitNotSupported = !task.isDockable;
+            boolean hideForExistingMultiWindow = activity.getDeviceProfile().isMultiWindowMode;
+
+            if (taskViewHasMultipleTasks ||
+                    notEnoughTasksToSplit ||
+                    isTaskSplitNotSupported ||
+                    hideForExistingMultiWindow ||
+                    (isFocusedTask && isTaskInExpectedScrollPosition)) {
                 return null;
             }
-            boolean dockTopOrLeft = navBarPosition != WindowManagerWrapper.NAV_BAR_POS_LEFT;
-            return ActivityOptionsCompat.makeSplitScreenOptions(dockTopOrLeft);
-        }
 
-        @Override
-        protected boolean onActivityStarted(BaseDraggingActivity activity) {
-            return true;
+            return orientationHandler.getSplitPositionOptions(deviceProfile)
+                    .stream()
+                    .map((Function<SplitPositionOption, SystemShortcut>) option ->
+                            new SplitSelectSystemShortcut(activity, taskView, option))
+                    .collect(Collectors.toList());
         }
     };
 
@@ -297,18 +328,22 @@
         }
     };
 
-    TaskShortcutFactory PIN = (activity, taskContainer) -> {
-        if (!SystemUiProxy.INSTANCE.get(activity).isActive()) {
-            return null;
+    TaskShortcutFactory PIN = new TaskShortcutFactory() {
+        @Override
+        public List<SystemShortcut> getShortcuts(BaseDraggingActivity activity,
+                TaskIdAttributeContainer taskContainer) {
+            if (!SystemUiProxy.INSTANCE.get(activity).isActive()) {
+                return null;
+            }
+            if (!ActivityManagerWrapper.getInstance().isScreenPinningEnabled()) {
+                return null;
+            }
+            if (ActivityManagerWrapper.getInstance().isLockToAppActive()) {
+                // We shouldn't be able to pin while an app is locked.
+                return null;
+            }
+            return Collections.singletonList(new PinSystemShortcut(activity, taskContainer));
         }
-        if (!ActivityManagerWrapper.getInstance().isScreenPinningEnabled()) {
-            return null;
-        }
-        if (ActivityManagerWrapper.getInstance().isLockToAppActive()) {
-            // We shouldn't be able to pin while an app is locked.
-            return null;
-        }
-        return new PinSystemShortcut(activity, taskContainer);
     };
 
     class PinSystemShortcut extends SystemShortcut<BaseDraggingActivity> {
@@ -335,26 +370,52 @@
         }
     }
 
-    TaskShortcutFactory INSTALL = (activity, taskContainer) ->
-            InstantAppResolver.newInstance(activity).isInstantApp(activity,
-                    taskContainer.getTask().getTopComponent().getPackageName())
-                    ? new SystemShortcut.Install(activity, taskContainer.getItemInfo(),
-                    taskContainer.getTaskView()) : null;
+    TaskShortcutFactory INSTALL = new TaskShortcutFactory() {
+        @Override
+        public List<SystemShortcut> getShortcuts(BaseDraggingActivity activity,
+                TaskIdAttributeContainer taskContainer) {
+            return InstantAppResolver.newInstance(activity).isInstantApp(activity,
+                    taskContainer.getTask().getTopComponent().getPackageName()) ?
+                    Collections.singletonList(new SystemShortcut.Install(activity,
+                            taskContainer.getItemInfo(), taskContainer.getTaskView())) :
+                    null;
+        }
+    };
 
-    TaskShortcutFactory WELLBEING = (activity, taskContainer) ->
-            WellbeingModel.SHORTCUT_FACTORY.getShortcut(activity, taskContainer.getItemInfo(),
-                    taskContainer.getTaskView());
+    TaskShortcutFactory WELLBEING = new TaskShortcutFactory() {
+        @Override
+        public List<SystemShortcut> getShortcuts(BaseDraggingActivity activity,
+                TaskIdAttributeContainer taskContainer) {
+            SystemShortcut<BaseDraggingActivity> wellbeingShortcut =
+                    WellbeingModel.SHORTCUT_FACTORY.getShortcut(activity,
+                            taskContainer.getItemInfo(), taskContainer.getTaskView());
+            return createSingletonShortcutList(wellbeingShortcut);
+        }
+    };
 
-    TaskShortcutFactory SCREENSHOT = (activity, taskContainer) ->
-            taskContainer.getThumbnailView().getTaskOverlay()
+    TaskShortcutFactory SCREENSHOT = new TaskShortcutFactory() {
+        @Override
+        public List<SystemShortcut> getShortcuts(BaseDraggingActivity activity,
+                TaskIdAttributeContainer taskContainer) {
+            SystemShortcut screenshotShortcut = taskContainer.getThumbnailView().getTaskOverlay()
                     .getScreenshotShortcut(activity, taskContainer.getItemInfo(),
                             taskContainer.getTaskView());
-
-    TaskShortcutFactory MODAL = (activity, taskContainer) -> {
-        if (ENABLE_OVERVIEW_SELECTIONS.get()) {
-            return taskContainer.getThumbnailView().getTaskOverlay().getModalStateSystemShortcut(
-                    taskContainer.getItemInfo(), taskContainer.getTaskView());
+            return createSingletonShortcutList(screenshotShortcut);
         }
-        return null;
+    };
+
+    TaskShortcutFactory MODAL = new TaskShortcutFactory() {
+        @Override
+        public List<SystemShortcut> getShortcuts(BaseDraggingActivity activity,
+                TaskIdAttributeContainer taskContainer) {
+            SystemShortcut modalStateSystemShortcut =
+                    taskContainer.getThumbnailView().getTaskOverlay()
+                            .getModalStateSystemShortcut(
+                                    taskContainer.getItemInfo(), taskContainer.getTaskView());
+            if (ENABLE_OVERVIEW_SELECTIONS.get()) {
+                return createSingletonShortcutList(modalStateSystemShortcut);
+            }
+            return null;
+        }
     };
 }
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index db402af..a030568 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -468,7 +468,6 @@
             animatorSet.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
-                    super.onAnimationEnd(animation);
                     finishCallback.run();
                 }
             });
@@ -530,7 +529,6 @@
                 for (SurfaceControl leash: closingTargets) {
                     t.hide(leash);
                 }
-                super.onAnimationEnd(animation);
                 finishCallback.run();
             }
         });
@@ -599,9 +597,14 @@
             launcherAnim.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
             launcherAnim.setDuration(RECENTS_LAUNCH_DURATION);
 
-            // Make sure recents gets fixed up by resetting task alphas and scales, etc.
             windowAnimEndListener = new AnimatorListenerAdapter() {
                 @Override
+                public void onAnimationStart(Animator animation) {
+                    recentsView.onTaskLaunchedInLiveTileMode();
+                }
+
+                // Make sure recents gets fixed up by resetting task alphas and scales, etc.
+                @Override
                 public void onAnimationEnd(Animator animation) {
                     recentsView.finishRecentsAnimation(false /* toRecents */, () -> {
                         recentsView.post(() -> {
@@ -677,7 +680,6 @@
         dockFadeAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
-                super.onAnimationStart(animation);
                 if (shown) {
                     for (SurfaceControl leash : auxiliarySurfaces) {
                         t.setAlpha(leash, 0);
@@ -689,7 +691,6 @@
 
             @Override
             public void onAnimationEnd(Animator animation) {
-                super.onAnimationEnd(animation);
                 if (!shown) {
                     for (SurfaceControl leash : auxiliarySurfaces) {
                         t.hide(leash);
diff --git a/quickstep/src/com/android/quickstep/TopTaskTracker.java b/quickstep/src/com/android/quickstep/TopTaskTracker.java
index 723dc72..42fa86d 100644
--- a/quickstep/src/com/android/quickstep/TopTaskTracker.java
+++ b/quickstep/src/com/android/quickstep/TopTaskTracker.java
@@ -33,7 +33,7 @@
 import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
 import com.android.launcher3.util.SplitConfigurationOptions.StageType;
-import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitTaskPosition;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitStageInfo;
 import com.android.launcher3.util.TraceHelper;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.Task.TaskKey;
@@ -63,8 +63,9 @@
     // Ordered list with first item being the most recent task.
     private final LinkedList<RunningTaskInfo> mOrderedTaskList = new LinkedList<>();
 
-    private final StagedSplitTaskPosition mMainStagePosition = new StagedSplitTaskPosition();
-    private final StagedSplitTaskPosition mSideStagePosition = new StagedSplitTaskPosition();
+
+    private final SplitStageInfo mMainStagePosition = new SplitStageInfo();
+    private final SplitStageInfo mSideStagePosition = new SplitStageInfo();
     private int mPinnedTaskId = INVALID_TASK_ID;
 
     private TopTaskTracker(Context context) {
@@ -144,8 +145,8 @@
         mPinnedTaskId = INVALID_TASK_ID;
     }
 
-    private void resetTaskId(StagedSplitTaskPosition taskPosition) {
-        taskPosition.taskId = INVALID_TASK_ID;
+    private void resetTaskId(SplitStageInfo taskPosition) {
+        taskPosition.taskId = -1;
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 61d36fb..149af98 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -51,6 +51,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.util.Log;
 import android.view.Choreographer;
 import android.view.InputEvent;
@@ -127,6 +128,9 @@
 
     private static final String TAG = "TouchInteractionService";
 
+    private static final boolean BUBBLES_HOME_GESTURE_ENABLED =
+            SystemProperties.getBoolean("persist.wm.debug.bubbles_home_gesture", true);
+
     private static final String KEY_BACK_NOTIFICATION_COUNT = "backNotificationCount";
     private static final String NOTIFY_ACTION_BACK = "com.android.quickstep.action.BACK_GESTURE";
     private static final String HAS_ENABLED_QUICKSTEP_ONCE = "launcher.has_enabled_quickstep_once";
@@ -723,16 +727,30 @@
                 base = new TaskbarStashInputConsumer(this, base, mInputMonitorCompat, tac);
             }
 
-            // If Bubbles is expanded, use the overlay input consumer, which will close Bubbles
-            // instead of going all the way home when a swipe up is detected.
-            // Notification panel can be expanded on top of expanded bubbles. Bubbles remain
-            // expanded in the back. Make sure swipe up is not passed to bubbles in this case.
-            if ((mDeviceState.isBubblesExpanded() && !mDeviceState.isNotificationPanelExpanded())
-                    || mDeviceState.isSystemUiDialogShowing()) {
+            if (mDeviceState.isBubblesExpanded()) {
+                if (BUBBLES_HOME_GESTURE_ENABLED) {
+                    // Bubbles can handle home gesture itself.
+                    base = getDefaultInputConsumer();
+                } else {
+                    // If Bubbles is expanded, use the overlay input consumer, which will close
+                    // Bubbles instead of going all the way home when a swipe up is detected.
+                    // Notification panel can be expanded on top of expanded bubbles. Bubbles remain
+                    // expanded in the back. Make sure swipe up is not passed to bubbles in this
+                    // case.
+                    if (!mDeviceState.isNotificationPanelExpanded()) {
+                        base = new SysUiOverlayInputConsumer(
+                                getBaseContext(), mDeviceState, mInputMonitorCompat);
+                    }
+                }
+            }
+
+            if (mDeviceState.isSystemUiDialogShowing()) {
                 base = new SysUiOverlayInputConsumer(
                         getBaseContext(), mDeviceState, mInputMonitorCompat);
             }
 
+
+
             if (mDeviceState.isScreenPinningActive()) {
                 // Note: we only allow accessibility to wrap this, and it replaces the previous
                 // base input consumer (which should be NO_OP anyway since topTaskLocked == true).
@@ -997,7 +1015,7 @@
             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);
         }
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index f68bbbc..c7c3441 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -27,6 +27,7 @@
 import static com.android.quickstep.fallback.RecentsState.OVERVIEW_SPLIT_SELECT;
 import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
 import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
+import static com.android.quickstep.views.RecentsView.OVERVIEW_PROGRESS;
 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_MODALNESS;
@@ -105,6 +106,8 @@
         boolean showAsGrid = state.displayOverviewTasksAsGrid(mActivity.getDeviceProfile());
         setter.setFloat(mRecentsView, RECENTS_GRID_PROGRESS, showAsGrid ? 1f : 0f,
                 showAsGrid ? INSTANT : FINAL_FRAME);
+        setter.setFloat(mRecentsView, OVERVIEW_PROGRESS, state == RecentsState.DEFAULT ? 1f : 0f,
+                INSTANT);
 
         setter.setViewBackgroundColor(mActivity.getScrimView(), state.getScrimColor(mActivity),
                 config.getInterpolator(ANIM_SCRIM_FADE, LINEAR));
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsState.java b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
index 77db6b7..af9d0cb 100644
--- a/quickstep/src/com/android/quickstep/fallback/RecentsState.java
+++ b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
@@ -15,6 +15,7 @@
  */
 package com.android.quickstep.fallback;
 
+import static com.android.launcher3.LauncherState.FLAG_CLOSE_POPUPS;
 import static com.android.launcher3.uioverrides.states.BackgroundAppState.getOverviewScaleAndOffsetForBackgroundState;
 import static com.android.launcher3.uioverrides.states.OverviewModalTaskState.getOverviewScaleAndOffsetForModalState;
 
@@ -52,7 +53,7 @@
     public static final RecentsState HOME = new RecentsState(3, 0);
     public static final RecentsState BG_LAUNCHER = new LauncherState(4, 0);
     public static final RecentsState OVERVIEW_SPLIT_SELECT = new RecentsState(5,
-            FLAG_SHOW_AS_GRID | FLAG_SCRIM | FLAG_OVERVIEW_UI);
+            FLAG_SHOW_AS_GRID | FLAG_SCRIM | FLAG_OVERVIEW_UI | FLAG_CLOSE_POPUPS);
 
     public final int ordinal;
     private final int mFlags;
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
index 3d737ca..c1750b5 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
@@ -19,12 +19,13 @@
 import static android.view.MotionEvent.ACTION_POINTER_DOWN;
 import static android.view.MotionEvent.ACTION_UP;
 
-import static com.android.launcher3.Utilities.createHomeIntent;
 import static com.android.launcher3.Utilities.squaredHypot;
 import static com.android.launcher3.Utilities.squaredTouchSlop;
 import static com.android.launcher3.util.VelocityUtils.PX_PER_MS;
 import static com.android.quickstep.AbsSwipeUpHandler.MIN_PROGRESS_FOR_OVERVIEW;
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
+import static com.android.quickstep.OverviewComponentObserver.startHomeIntentSafely;
+import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
 import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
 
 import android.animation.Animator;
@@ -101,6 +102,8 @@
 
     private boolean mThresholdCrossed = false;
     private boolean mHomeLaunched = false;
+    private boolean mCancelWhenRecentsStart = false;
+    private boolean mDismissTask = false;
 
     private RecentsAnimationController mRecentsAnimationController;
 
@@ -204,9 +207,20 @@
             animator.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
-                    if (dismissTask) {
-                        // For now, just start the home intent so user is prompted to unlock the device.
-                        mContext.startActivity(createHomeIntent());
+                    if (ENABLE_SHELL_TRANSITIONS) {
+                        if (mTaskAnimationManager.getCurrentCallbacks() != null) {
+                            if (mRecentsAnimationController != null) {
+                                finishRecentsAnimationForShell(dismissTask);
+                            } else {
+                                // the transition of recents animation hasn't started, wait for it
+                                mCancelWhenRecentsStart = true;
+                                mDismissTask = dismissTask;
+                            }
+                        }
+                    } else if (dismissTask) {
+                        // For now, just start the home intent so user is prompted to
+                        // unlock the device.
+                        startHomeIntentSafely(mContext, mGestureState.getHomeIntent(), null);
                         mHomeLaunched = true;
                     }
                     mStateCallback.setState(STATE_HANDLER_INVALIDATED);
@@ -238,12 +252,24 @@
         mTransformParams.setTargetSet(targets);
         applyTransform();
         mStateCallback.setState(STATE_TARGET_RECEIVED);
+        if (mCancelWhenRecentsStart) {
+            finishRecentsAnimationForShell(mDismissTask);
+        }
     }
 
     @Override
     public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
         mRecentsAnimationController = null;
         mTransformParams.setTargetSet(null);
+        mCancelWhenRecentsStart = false;
+    }
+
+    private void finishRecentsAnimationForShell(boolean dismissTask) {
+        mCancelWhenRecentsStart = false;
+        mTaskAnimationManager.finishRunningRecentsAnimation(dismissTask /* toHome */);
+        if (dismissTask) {
+            mHomeLaunched = true;
+        }
     }
 
     private void endRemoteAnimation() {
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index e458c1f..641385d 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -27,6 +27,7 @@
 import static com.android.launcher3.PagedView.DEBUG_FAILED_QUICKSWITCH;
 import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
 import static com.android.launcher3.Utilities.squaredHypot;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS;
 import static com.android.launcher3.util.VelocityUtils.PX_PER_MS;
 import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
@@ -47,6 +48,7 @@
 import androidx.annotation.UiThread;
 
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.tracing.InputConsumerProto;
@@ -58,7 +60,9 @@
 import com.android.quickstep.GestureState;
 import com.android.quickstep.InputConsumer;
 import com.android.quickstep.RecentsAnimationCallbacks;
+import com.android.quickstep.RecentsAnimationController;
 import com.android.quickstep.RecentsAnimationDeviceState;
+import com.android.quickstep.RecentsAnimationTargets;
 import com.android.quickstep.RotationTouchHelper;
 import com.android.quickstep.TaskAnimationManager;
 import com.android.quickstep.util.ActiveGestureLog;
@@ -107,6 +111,7 @@
     private VelocityTracker mVelocityTracker;
 
     private AbsSwipeUpHandler mInteractionHandler;
+    private final FinishImmediatelyHandler mCleanupHandler = new FinishImmediatelyHandler();
 
     private final boolean mIsDeferredDownTarget;
     private final PointF mDownPos = new PointF();
@@ -377,6 +382,7 @@
 
         if (mTaskAnimationManager.isRecentsAnimationRunning()) {
             mActiveCallbacks = mTaskAnimationManager.continueRecentsAnimation(mGestureState);
+            mActiveCallbacks.removeListener(mCleanupHandler);
             mActiveCallbacks.addListener(mInteractionHandler);
             mTaskAnimationManager.notifyRecentsAnimationState(mInteractionHandler);
             notifyGestureStarted(true /*isLikelyToStartNewTask*/);
@@ -414,7 +420,19 @@
             }
         } else {
             // Since we start touch tracking on DOWN, we may reach this state without actually
-            // starting the gesture. In that case, just cleanup immediately.
+            // starting the gesture. In that case, we need to clean-up an unfinished or un-started
+            // animation.
+            if (mActiveCallbacks != null && mInteractionHandler != null) {
+                if (mTaskAnimationManager.isRecentsAnimationRunning()) {
+                    // The animation started, but with no movement, in this case, there will be no
+                    // animateToProgress so we have to manually finish here.
+                    mTaskAnimationManager.finishRunningRecentsAnimation(false /* toHome */);
+                } else {
+                    // The animation hasn't started yet, so insert a replacement handler into the
+                    // callbacks which immediately finishes the animation after it starts.
+                    mActiveCallbacks.addListener(mCleanupHandler);
+                }
+            }
             onConsumerAboutToBeSwitched();
             onInteractionGestureFinished();
 
@@ -464,7 +482,7 @@
     }
 
     private void removeListener() {
-        if (mActiveCallbacks != null) {
+        if (mActiveCallbacks != null && mInteractionHandler != null) {
             mActiveCallbacks.removeListener(mInteractionHandler);
         }
     }
@@ -490,4 +508,19 @@
             mInteractionHandler.writeToProto(inputConsumerProto);
         }
     }
+
+    /**
+     * A listener which just finishes the animation immediately after starting. Replaces
+     * AbsSwipeUpHandler if the gesture itself finishes before the animation even starts.
+     */
+    private static class FinishImmediatelyHandler
+            implements RecentsAnimationCallbacks.RecentsAnimationListener {
+
+        public void onRecentsAnimationStart(RecentsAnimationController controller,
+                RecentsAnimationTargets targets) {
+            Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
+                controller.finish(false /* toRecents */, null);
+            });
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
index 864e08d..a730183 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
@@ -15,12 +15,11 @@
  */
 package com.android.quickstep.inputconsumers;
 
-import static com.android.launcher3.Utilities.createHomeIntent;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
+import static com.android.quickstep.OverviewComponentObserver.startHomeIntentSafely;
 
-import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.graphics.PointF;
 import android.view.MotionEvent;
@@ -79,11 +78,7 @@
 
     @Override
     public void onSwipeUp(boolean wasFling, PointF finalVelocity) {
-        try {
-            mContext.startActivity(mGestureState.getHomeIntent());
-        } catch (NullPointerException | ActivityNotFoundException | SecurityException e) {
-            mContext.startActivity(createHomeIntent());
-        }
+        startHomeIntentSafely(mContext, mGestureState.getHomeIntent(), null);
         ActiveGestureLog.INSTANCE.addLog("startQuickstep");
         BaseActivity activity = BaseDraggingActivity.fromContext(mContext);
         int state = (mGestureState != null && mGestureState.getEndTarget() != null)
diff --git a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
index 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/util/AnimatorControllerWithResistance.java b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
index 7c83833..ded4fb0 100644
--- a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
+++ b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
@@ -36,7 +36,6 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.quickstep.LauncherActivityInterface;
 import com.android.quickstep.views.RecentsView;
 
 /**
@@ -155,10 +154,9 @@
      */
     public static <SCALE, TRANSLATION> PendingAnimation createRecentsResistanceAnim(
             RecentsParams<SCALE, TRANSLATION> params) {
-        Rect startRect = new Rect();
+        Rect startRect = new Rect(params.dp.overviewTaskRect);
         PagedOrientationHandler orientationHandler = params.recentsOrientedState
                 .getOrientationHandler();
-        LauncherActivityInterface.INSTANCE.calculateTaskSize(params.context, params.dp, startRect);
         long distanceToCover = startRect.bottom;
         PendingAnimation resistAnim = params.resistAnim != null
                 ? params.resistAnim
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..d4eb880 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -16,7 +16,6 @@
 package com.android.quickstep.util;
 
 import android.content.Context;
-import android.graphics.Rect;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -24,7 +23,6 @@
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.NavigationMode;
-import com.android.quickstep.LauncherActivityInterface;
 
 public class LayoutUtils {
 
@@ -39,12 +37,12 @@
         return swipeHeight;
     }
 
-    public static int getShelfTrackingDistance(Context context, DeviceProfile dp,
+    /**
+     *  Returns the shelf tracking distance.
+     */
+    public static int getShelfTrackingDistance(DeviceProfile dp,
             PagedOrientationHandler orientationHandler) {
-        // Track the bottom of the window.
-        Rect taskSize = new Rect();
-        LauncherActivityInterface.INSTANCE.calculateTaskSize(context, dp, taskSize);
-        return orientationHandler.getDistanceToBottomOfRect(dp, taskSize);
+        return orientationHandler.getDistanceToBottomOfRect(dp, dp.overviewTaskRect);
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
index b83e26e..e758f5b 100644
--- a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
+++ b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
@@ -17,13 +17,14 @@
 
 import android.content.Context;
 import android.content.res.Resources;
+import android.util.Log;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 
 import com.android.launcher3.Alarm;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
-import com.android.launcher3.testing.TestProtocol;
 
 /**
  * Given positions along x- or y-axis, tracks velocity and acceleration and determines when there is
@@ -31,6 +32,8 @@
  */
 public class MotionPauseDetector {
 
+    private static final String TAG = "MotionPauseDetector";
+
     // The percentage of the previous speed that determines whether this is a rapid deceleration.
     // The bigger this number, the easier it is to trigger the first pause.
     private static final float RAPID_DECELERATION_FACTOR = 0.6f;
@@ -43,6 +46,12 @@
      */
     private static final long HARDER_TRIGGER_TIMEOUT = 400;
 
+    /**
+     * When running in a test harness, if no motion is added for this amount of time, assume the
+     * motion has paused. (We use an increased timeout since sometimes test devices can be slow.)
+     */
+    private static final long TEST_HARNESS_TRIGGER_TIMEOUT = 2000;
+
     private final float mSpeedVerySlow;
     private final float mSpeedSlow;
     private final float mSpeedSomewhatFast;
@@ -85,7 +94,8 @@
         mSpeedSomewhatFast = res.getDimension(R.dimen.motion_pause_detector_speed_somewhat_fast);
         mSpeedFast = res.getDimension(R.dimen.motion_pause_detector_speed_fast);
         mForcePauseTimeout = new Alarm();
-        mForcePauseTimeout.setOnAlarmListener(alarm -> updatePaused(true /* isPaused */));
+        mForcePauseTimeout.setOnAlarmListener(alarm -> updatePaused(true /* isPaused */,
+                "Force pause timeout after " +  alarm.getLastSetTimeout() + "ms" /* reason */));
         mMakePauseHarderToTrigger = makePauseHarderToTrigger;
         mVelocityProvider = new SystemVelocityProvider(axis);
     }
@@ -102,7 +112,7 @@
      */
     public void setDisallowPause(boolean disallowPause) {
         mDisallowPause = disallowPause;
-        updatePaused(mIsPaused);
+        updatePaused(mIsPaused, "Set disallowPause=" + disallowPause);
     }
 
     /**
@@ -119,9 +129,11 @@
      * @param pointerIndex Index for the pointer being tracked in the motion event
      */
     public void addPosition(MotionEvent ev, int pointerIndex) {
-        long timeoutMs = TestProtocol.sForcePauseTimeout != null
-                ? TestProtocol.sForcePauseTimeout
-                : mMakePauseHarderToTrigger ? HARDER_TRIGGER_TIMEOUT : FORCE_PAUSE_TIMEOUT;
+        long timeoutMs = Utilities.IS_RUNNING_IN_TEST_HARNESS
+                ? TEST_HARNESS_TRIGGER_TIMEOUT
+                : mMakePauseHarderToTrigger
+                        ? HARDER_TRIGGER_TIMEOUT
+                        : FORCE_PAUSE_TIMEOUT;
         mForcePauseTimeout.setAlarm(timeoutMs);
         float newVelocity = mVelocityProvider.addMotionEvent(ev, ev.getPointerId(pointerIndex));
         if (mPreviousVelocity != null) {
@@ -134,21 +146,27 @@
         float speed = Math.abs(velocity);
         float previousSpeed = Math.abs(prevVelocity);
         boolean isPaused;
+        String isPausedReason = "";
         if (mIsPaused) {
             // Continue to be paused until moving at a fast speed.
             isPaused = speed < mSpeedFast || previousSpeed < mSpeedFast;
+            isPausedReason = "Was paused, but started moving at a fast speed";
         } else {
             if (velocity < 0 != prevVelocity < 0) {
                 // We're just changing directions, not necessarily stopping.
                 isPaused = false;
+                isPausedReason = "Velocity changed directions";
             } else {
                 isPaused = speed < mSpeedVerySlow && previousSpeed < mSpeedVerySlow;
+                isPausedReason = "Pause requires back to back slow speeds";
                 if (!isPaused && !mHasEverBeenPaused) {
                     // We want to be more aggressive about detecting the first pause to ensure it
                     // feels as responsive as possible; getting two very slow speeds back to back
                     // takes too long, so also check for a rapid deceleration.
                     boolean isRapidDeceleration = speed < previousSpeed * RAPID_DECELERATION_FACTOR;
                     isPaused = isRapidDeceleration && speed < mSpeedSomewhatFast;
+                    isPausedReason = "Didn't have back to back slow speeds, checking for rapid"
+                            + " deceleration on first pause only";
                 }
                 if (mMakePauseHarderToTrigger) {
                     if (speed < mSpeedSlow) {
@@ -156,22 +174,27 @@
                             mSlowStartTime = time;
                         }
                         isPaused = time - mSlowStartTime >= HARDER_TRIGGER_TIMEOUT;
+                        isPausedReason = "Maintained slow speed for sufficient duration when making"
+                                + " pause harder to trigger";
                     } else {
                         mSlowStartTime = 0;
                         isPaused = false;
+                        isPausedReason = "Intentionally making pause harder to trigger";
                     }
                 }
             }
         }
-        updatePaused(isPaused);
+        updatePaused(isPaused, isPausedReason);
     }
 
-    private void updatePaused(boolean isPaused) {
+    private void updatePaused(boolean isPaused, String reason) {
         if (mDisallowPause) {
+            reason = "Disallow pause; otherwise, would have been " + isPaused + " due to " + reason;
             isPaused = false;
         }
         if (mIsPaused != isPaused) {
             mIsPaused = isPaused;
+            Log.d(TAG, "onMotionPauseChanged, paused=" + mIsPaused + " reason=" + reason);
             boolean isFirstDetectedPause = !mHasEverBeenPaused && mIsPaused;
             if (mIsPaused) {
                 AccessibilityManagerCompat.sendPauseDetectedEventToTest(mContext);
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index 6038a22..39e3797 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -25,7 +25,6 @@
 import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.SettingsCache.ROTATION_SETTING_URI;
-import static com.android.quickstep.BaseActivityInterface.getTaskDimension;
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
@@ -53,7 +52,6 @@
 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 +219,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 +397,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);
-        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;
-        }
-
+        float scale = Math.min(dp.taskDimension.x / taskView.width(),
+                dp.taskDimension.y / taskView.height());
         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/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index 2502359..e89c842 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -42,6 +42,8 @@
 
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statemanager.StateManager;
+import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
 import com.android.quickstep.SystemUiProxy;
@@ -171,6 +173,8 @@
     public void launchTasks(int taskId1, @Nullable PendingIntent taskPendingIntent,
             @Nullable Intent fillInIntent, int taskId2, @StagePosition int stagePosition,
             Consumer<Boolean> callback, boolean freezeTaskList, float splitRatio) {
+        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}
@@ -330,4 +334,8 @@
     private boolean isInitialTaskIntentSet() {
         return (mInitialTaskId != INVALID_TASK_ID || mInitialTaskIntent != null);
     }
+
+    public int getInitialTaskId() {
+        return mInitialTaskId;
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index 32e08ff..de527a7 100644
--- a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -41,6 +41,7 @@
 import com.android.launcher3.Hotseat;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.QuickstepTransitionManager;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutAndWidgetContainer;
 import com.android.launcher3.Workspace;
@@ -60,10 +61,10 @@
 public class StaggeredWorkspaceAnim {
 
     private static final int APP_CLOSE_ROW_START_DELAY_MS = 10;
-    // How long it takes to fade in each staggered row.
-    private static final int ALPHA_DURATION_MS = 250;
     // Should be used for animations running alongside this StaggeredWorkspaceAnim.
     public static final int DURATION_MS = 250;
+    public static final int DURATION_TASKBAR_MS =
+            QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION;
 
     private static final float MAX_VELOCITY_PX_PER_S = 22f;
 
@@ -91,16 +92,20 @@
         mSpringTransY = transFactor * launcher.getResources()
                 .getDimensionPixelSize(R.dimen.swipe_up_max_workspace_trans_y);
 
+        DeviceProfile grid = launcher.getDeviceProfile();
+        long duration = grid.isTaskbarPresent ? DURATION_TASKBAR_MS : DURATION_MS;
         if (staggerWorkspace) {
-            DeviceProfile grid = launcher.getDeviceProfile();
             Workspace<?> workspace = launcher.getWorkspace();
             Hotseat hotseat = launcher.getHotseat();
 
-            // Hotseat and QSB takes up two additional rows.
-            int totalRows = grid.inv.numRows + (grid.isVerticalBarLayout() ? 0 : 2);
+            boolean staggerHotseat = !grid.isVerticalBarLayout() && !grid.isTaskbarPresent;
+            boolean staggerQsb =
+                    !grid.isVerticalBarLayout() && !(grid.isTaskbarPresent && grid.isQsbInline);
+            int totalRows = grid.inv.numRows + (staggerHotseat ? 1 : 0) + (staggerQsb ? 1 : 0);
 
             // Add animation for all the visible workspace pages
-            workspace.forEachVisiblePage(page -> addAnimationForPage((CellLayout) page, totalRows));
+            workspace.forEachVisiblePage(
+                    page -> addAnimationForPage((CellLayout) page, totalRows, duration));
 
             boolean workspaceClipChildren = workspace.getClipChildren();
             boolean workspaceClipToPadding = workspace.getClipToPadding();
@@ -119,23 +124,34 @@
                     View child = hotseatIcons.getChildAt(i);
                     CellLayout.LayoutParams lp =
                             ((CellLayout.LayoutParams) child.getLayoutParams());
-                    addStaggeredAnimationForView(child, lp.cellY + 1, totalRows);
+                    addStaggeredAnimationForView(child, lp.cellY + 1, totalRows, duration);
                 }
             } else {
                 final int hotseatRow, qsbRow;
                 if (grid.isTaskbarPresent) {
-                    qsbRow = grid.inv.numRows + 1;
-                    hotseatRow = grid.inv.numRows + 2;
+                    if (grid.isQsbInline) {
+                        qsbRow = grid.inv.numRows + 1;
+                        hotseatRow = grid.inv.numRows + 1;
+                    } else {
+                        qsbRow = grid.inv.numRows + 1;
+                        hotseatRow = grid.inv.numRows + 2;
+                    }
                 } else {
                     hotseatRow = grid.inv.numRows + 1;
                     qsbRow = grid.inv.numRows + 2;
                 }
-                for (int i = hotseatIcons.getChildCount() - 1; i >= 0; i--) {
-                    View child = hotseatIcons.getChildAt(i);
-                    addStaggeredAnimationForView(child, hotseatRow, totalRows);
-                }
 
-                addStaggeredAnimationForView(hotseat.getQsb(), qsbRow, totalRows);
+                // Do not stagger hotseat as a whole when taskbar is present, and stagger QSB only
+                // if it's not inline.
+                if (staggerHotseat) {
+                    for (int i = hotseatIcons.getChildCount() - 1; i >= 0; i--) {
+                        View child = hotseatIcons.getChildAt(i);
+                        addStaggeredAnimationForView(child, hotseatRow, totalRows, duration);
+                    }
+                }
+                if (staggerQsb) {
+                    addStaggeredAnimationForView(hotseat.getQsb(), qsbRow, totalRows, duration);
+                }
             }
 
             mAnimators.addListener(new AnimatorListenerAdapter() {
@@ -153,19 +169,19 @@
         mAnimators.addListener(forEndCallback(launcher::resumeExpensiveViewUpdates));
 
         if (animateOverviewScrim) {
-            PendingAnimation pendingAnimation = new PendingAnimation(DURATION_MS);
+            PendingAnimation pendingAnimation = new PendingAnimation(duration);
             launcher.getWorkspace().getStateTransitionAnimation()
                     .setScrim(pendingAnimation, NORMAL, new StateAnimationConfig());
             mAnimators.play(pendingAnimation.buildAnim());
         }
 
-        addDepthAnimationForState(launcher, NORMAL, DURATION_MS);
+        addDepthAnimationForState(launcher, NORMAL, duration);
 
         mAnimators.play(launcher.getRootView().getSysUiScrim().createSysuiMultiplierAnim(0f, 1f)
-                .setDuration(DURATION_MS));
+                .setDuration(duration));
     }
 
-    private void addAnimationForPage(CellLayout page, int totalRows) {
+    private void addAnimationForPage(CellLayout page, int totalRows, long duration) {
         ShortcutAndWidgetContainer itemsContainer = page.getShortcutsAndWidgets();
 
         boolean pageClipChildren = page.getClipChildren();
@@ -178,7 +194,7 @@
         for (int i = itemsContainer.getChildCount() - 1; i >= 0; i--) {
             View child = itemsContainer.getChildAt(i);
             CellLayout.LayoutParams lp = ((CellLayout.LayoutParams) child.getLayoutParams());
-            addStaggeredAnimationForView(child, lp.cellY + lp.cellVSpan, totalRows);
+            addStaggeredAnimationForView(child, lp.cellY + lp.cellVSpan, totalRows, duration);
         }
 
         mAnimators.addListener(new AnimatorListenerAdapter() {
@@ -231,8 +247,9 @@
      * @param v A view on the workspace.
      * @param row The bottom-most row that contains the view.
      * @param totalRows Total number of rows.
+     * @param duration duration of the animation
      */
-    private void addStaggeredAnimationForView(View v, int row, int totalRows) {
+    private void addStaggeredAnimationForView(View v, int row, int totalRows, long duration) {
         if (mIgnoredView != null && mIgnoredView == v) return;
         // Invert the rows, because we stagger starting from the bottom of the screen.
         int invertedRow = totalRows - row;
@@ -266,7 +283,7 @@
         v.setAlpha(0);
         ObjectAnimator alpha = ObjectAnimator.ofFloat(v, View.ALPHA, 0f, 1f);
         alpha.setInterpolator(LINEAR);
-        alpha.setDuration(ALPHA_DURATION_MS);
+        alpha.setDuration(duration);
         alpha.setStartDelay(startDelay);
         alpha.addListener(new AnimatorListenerAdapter() {
             @Override
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
index b222f51..74e4acc 100644
--- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -253,7 +253,7 @@
                     rotatedPosition.degree, rotatedPosition.positionX, rotatedPosition.positionY);
         } else {
             return mSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mSourceRectHint, mAppBounds,
-                    bounds, insets);
+                    bounds, insets, progress);
         }
     }
 
@@ -279,7 +279,10 @@
         // get the final leash operations but do not apply to the leash.
         final SurfaceControl.Transaction tx =
                 PipSurfaceTransactionHelper.newSurfaceControlTransaction();
-        return onAnimationUpdate(tx, new RectF(mDestinationBounds), END_PROGRESS);
+        final PictureInPictureSurfaceTransaction pipTx =
+                onAnimationUpdate(tx, new RectF(mDestinationBounds), END_PROGRESS);
+        pipTx.setShouldDisableCanAffectSystemUiFlags(true);
+        return pipTx;
     }
 
     private RotatedPosition getRotatedPosition(float progress) {
diff --git a/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java b/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java
index 9bb3d56..5dc4613 100644
--- a/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java
+++ b/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java
@@ -15,12 +15,20 @@
  */
 package com.android.quickstep.util;
 
-import android.content.Context;
-import android.hardware.display.DisplayManager;
-import android.view.Display;
+import static android.view.Display.DEFAULT_DISPLAY;
 
+import android.content.Context;
+import android.util.ArrayMap;
+import android.view.Surface;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
+
+import com.android.launcher3.util.WindowBounds;
+import com.android.launcher3.util.window.CachedDisplayInfo;
 import com.android.launcher3.util.window.WindowManagerProxy;
 
+import java.util.Set;
+
 /**
  * Extension of {@link WindowManagerProxy} with some assumption for the default system Launcher
  */
@@ -31,23 +39,23 @@
     }
 
     @Override
-    protected String getDisplayId(Display display) {
-        return display.getUniqueId();
+    public int getRotation(Context displayInfoContext) {
+        return displayInfoContext.getResources().getConfiguration().windowConfiguration
+                .getRotation();
     }
 
     @Override
-    public boolean isInternalDisplay(Display display) {
-        return display.getType() == Display.TYPE_INTERNAL;
-    }
-
-    @Override
-    public int getRotation(Context context) {
-        return context.getResources().getConfiguration().windowConfiguration.getRotation();
-    }
-
-    @Override
-    protected Display[] getDisplays(Context context) {
-        return context.getSystemService(DisplayManager.class).getDisplays(
-                DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED);
+    public ArrayMap<CachedDisplayInfo, WindowBounds[]> estimateInternalDisplayBounds(
+            Context displayInfoContext) {
+        ArrayMap<CachedDisplayInfo, WindowBounds[]> result = new ArrayMap<>();
+        WindowManager windowManager = displayInfoContext.getSystemService(WindowManager.class);
+        Set<WindowMetrics> possibleMaximumWindowMetrics =
+                windowManager.getPossibleMaximumWindowMetrics(DEFAULT_DISPLAY);
+        for (WindowMetrics windowMetrics : possibleMaximumWindowMetrics) {
+            CachedDisplayInfo info = getDisplayInfo(windowMetrics, Surface.ROTATION_0);
+            WindowBounds[] bounds = estimateWindowBounds(displayInfoContext, info);
+            result.put(info, bounds);
+        }
+        return result;
     }
 }
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 5212755..c626b0a 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -40,7 +40,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
 import com.android.launcher3.util.TraceHelper;
 import com.android.quickstep.AnimatedFloat;
 import com.android.quickstep.BaseActivityInterface;
@@ -100,7 +100,7 @@
     // Cached calculations
     private boolean mLayoutValid = false;
     private int mOrientationStateId;
-    private StagedSplitBounds mStagedSplitBounds;
+    private SplitBounds mSplitBounds;
     private boolean mDrawsBelowRecents;
     private boolean mIsGridTask;
     private int mTaskRectTranslationX;
@@ -144,21 +144,17 @@
         if (mDp == null) {
             return 1;
         }
-        if (mIsGridTask) {
-            mSizeStrategy.calculateGridTaskSize(mContext, mDp, mTaskRect,
-                    mOrientationState.getOrientationHandler());
-        } else {
-            mSizeStrategy.calculateTaskSize(mContext, mDp, mTaskRect);
-        }
+        mTaskRect.set(
+                mIsGridTask ? mDp.getOverviewGridTaskRect(mIsRecentsRtl) : mDp.overviewTaskRect);
 
         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 +176,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 +386,8 @@
                 .withWindowCrop(mTmpCropRect)
                 .withCornerRadius(getCurrentCornerRadius());
 
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && params.getRecentsSurface() != null) {
-            // When relativeLayer = 0, it reverts the surfaces back to the original order.
-            builder.withRelativeLayerTo(params.getRecentsSurface(),
-                    mDrawsBelowRecents ? Integer.MIN_VALUE : 0);
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            builder.withLayer(mDrawsBelowRecents ? Integer.MIN_VALUE + 1 : Integer.MAX_VALUE);
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/util/TaskVisualsChangeListener.java b/quickstep/src/com/android/quickstep/util/TaskVisualsChangeListener.java
new file mode 100644
index 0000000..66bff73
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/TaskVisualsChangeListener.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util;
+
+import android.os.UserHandle;
+
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+
+/**
+ * Listener for receiving various task properties changes
+ */
+public interface TaskVisualsChangeListener {
+
+    /**
+     * Called when the task thumbnail changes
+     */
+    default Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData) {
+        return null;
+    }
+
+    /**
+     * Called when the icon for a task changes
+     */
+    default void onTaskIconChanged(String pkg, UserHandle user) {}
+
+    /**
+     * Called when the icon for a task changes
+     */
+    default void onTaskIconChanged(int taskId) {}
+}
diff --git a/quickstep/src/com/android/quickstep/util/TransformParams.java b/quickstep/src/com/android/quickstep/util/TransformParams.java
index 75d6001..a7f25d4 100644
--- a/quickstep/src/com/android/quickstep/util/TransformParams.java
+++ b/quickstep/src/com/android/quickstep/util/TransformParams.java
@@ -139,10 +139,12 @@
 
     public SurfaceParams[] createSurfaceParams(BuilderProxy proxy) {
         RemoteAnimationTargets targets = mTargetSet;
-        SurfaceParams[] surfaceParams = new SurfaceParams[targets.unfilteredApps.length];
+        final int appLength =  targets.unfilteredApps.length;
+        final int wallpaperLength = targets.wallpapers != null ? targets.wallpapers.length : 0;
+        SurfaceParams[] surfaceParams = new SurfaceParams[appLength + wallpaperLength];
         mRecentsSurface = getRecentsSurface(targets);
 
-        for (int i = 0; i < targets.unfilteredApps.length; i++) {
+        for (int i = 0; i < appLength; i++) {
             RemoteAnimationTargetCompat app = targets.unfilteredApps[i];
             SurfaceParams.Builder builder = new SurfaceParams.Builder(app.leash);
 
@@ -166,6 +168,12 @@
             }
             surfaceParams[i] = builder.build();
         }
+        // always put wallpaper layer to bottom.
+        for (int i = 0; i < wallpaperLength; i++) {
+            RemoteAnimationTargetCompat wallpaper = targets.wallpapers[i];
+            surfaceParams[appLength + i] = new SurfaceParams.Builder(wallpaper.leash)
+                    .withLayer(Integer.MIN_VALUE).build();
+        }
         return surfaceParams;
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
index 79b15c7..76552a3 100644
--- a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
+++ b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
@@ -53,7 +53,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
 import com.android.systemui.shared.recents.model.Task;
 
 import java.lang.annotation.Retention;
@@ -103,7 +103,7 @@
      */
     private float mModalOffset = 0f;
     @Nullable
-    private StagedSplitBounds mStagedSplitBounds;
+    private SplitBounds mSplitBounds;
     private int mSplitBannerConfig = SPLIT_BANNER_FULLSCREEN;
     private float mSplitOffsetTranslationY;
     private float mSplitOffsetTranslationX;
@@ -164,9 +164,9 @@
         });
     }
 
-    public void setSplitConfiguration(StagedSplitBounds stagedSplitBounds) {
-        mStagedSplitBounds = stagedSplitBounds;
-        if (mStagedSplitBounds == null
+    public void setSplitConfiguration(SplitBounds splitBounds) {
+        mSplitBounds = splitBounds;
+        if (mSplitBounds == null
                 || !mActivity.getDeviceProfile().isTablet
                 || mTaskView.isFocusedTask()) {
             mSplitBannerConfig = SPLIT_BANNER_FULLSCREEN;
@@ -180,11 +180,11 @@
         }
 
         // For landscape grid, for 30% width we only show icon, otherwise show icon and time
-        if (mTask.key.id == mStagedSplitBounds.leftTopTaskId) {
-            mSplitBannerConfig = mStagedSplitBounds.leftTaskPercent < THRESHOLD_LEFT_ICON_ONLY ?
+        if (mTask.key.id == mSplitBounds.leftTopTaskId) {
+            mSplitBannerConfig = mSplitBounds.leftTaskPercent < THRESHOLD_LEFT_ICON_ONLY ?
                     SPLIT_GRID_BANNER_SMALL : SPLIT_GRID_BANNER_LARGE;
         } else {
-            mSplitBannerConfig = mStagedSplitBounds.leftTaskPercent > THRESHOLD_RIGHT_ICON_ONLY ?
+            mSplitBannerConfig = mSplitBounds.leftTaskPercent > THRESHOLD_RIGHT_ICON_ONLY ?
                     SPLIT_GRID_BANNER_SMALL : SPLIT_GRID_BANNER_LARGE;
         }
     }
@@ -321,7 +321,7 @@
         PagedOrientationHandler orientationHandler = mTaskView.getPagedOrientationHandler();
         Pair<Float, Float> translations = orientationHandler
                 .getDwbLayoutTranslations(mTaskView.getMeasuredWidth(),
-                        mTaskView.getMeasuredHeight(), mStagedSplitBounds, deviceProfile,
+                        mTaskView.getMeasuredHeight(), mSplitBounds, deviceProfile,
                         mTaskView.getThumbnails(), mTask.key.id, mBanner);
         mSplitOffsetTranslationX = translations.first;
         mSplitOffsetTranslationY = translations.second;
diff --git a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
index c980d1e..835c9f7 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
@@ -1,5 +1,6 @@
 package com.android.quickstep.views;
 
+import static com.android.launcher3.AbstractFloatingView.TYPE_TASK_MENU;
 import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
@@ -19,6 +20,7 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.InsettableFrameLayout;
 import com.android.launcher3.LauncherAnimUtils;
@@ -110,13 +112,19 @@
      */
     public static FloatingTaskView getFloatingTaskView(StatefulActivity launcher,
             View originalView, @Nullable Bitmap thumbnail, Drawable icon, RectF positionOut) {
-        final BaseDragLayer dragLayer = launcher.getDragLayer();
-        ViewGroup parent = (ViewGroup) dragLayer.getParent();
+        final ViewGroup dragLayer = launcher.getDragLayer();
         final FloatingTaskView floatingView = (FloatingTaskView) launcher.getLayoutInflater()
-                .inflate(R.layout.floating_split_select_view, parent, false);
+                .inflate(R.layout.floating_split_select_view, dragLayer, false);
 
         floatingView.init(launcher, originalView, thumbnail, icon, positionOut);
-        parent.addView(floatingView);
+        // Add this animating view underneath the existing open task menu view (if there is one)
+        View openTaskView = AbstractFloatingView.getOpenView(launcher, TYPE_TASK_MENU);
+        int openTaskViewIndex = dragLayer.indexOfChild(openTaskView);
+        if (openTaskViewIndex == -1) {
+            // Add to top if not
+            openTaskViewIndex = dragLayer.getChildCount();
+        }
+        dragLayer.addView(floatingView, openTaskViewIndex - 1);
         return floatingView;
     }
 
@@ -125,7 +133,7 @@
         Utilities.getBoundsForViewInDragLayer(mActivity.getDragLayer(), originalView, viewBounds,
                 false /* ignoreTransform */, null /* recycle */,
                 mStartingPosition);
-        final InsettableFrameLayout.LayoutParams lp = new InsettableFrameLayout.LayoutParams(
+        final BaseDragLayer.LayoutParams lp = new BaseDragLayer.LayoutParams(
                 Math.round(mStartingPosition.width()),
                 Math.round(mStartingPosition.height()));
         initPosition(mStartingPosition, lp);
@@ -246,7 +254,7 @@
      *                        offscreen).
      */
     void centerIconView(IconView iconView, float onScreenRectCenterX, float onScreenRectCenterY) {
-        mOrientationHandler.updateStagedSplitIconParams(iconView, onScreenRectCenterX,
+        mOrientationHandler.updateSplitIconParams(iconView, onScreenRectCenterX,
                 onScreenRectCenterY, mFullscreenParams.mScaleX, mFullscreenParams.mScaleY,
                 iconView.getDrawableWidth(), iconView.getDrawableHeight(),
                 mActivity.getDeviceProfile(), mStagePosition);
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index 244a794..2dff18e 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.anim.Interpolators.LINEAR;
 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;
@@ -17,7 +18,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.RunnableList;
-import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
 import com.android.launcher3.util.TransformingTouchDelegate;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.TaskIconCache;
@@ -53,7 +54,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 +79,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 +127,8 @@
         }
     }
 
-    public void updateSplitBoundsConfig(StagedSplitBounds stagedSplitBounds) {
-        mSplitBoundsConfig = stagedSplitBounds;
+    public void updateSplitBoundsConfig(SplitBounds splitBounds) {
+        mSplitBoundsConfig = splitBounds;
         invalidate();
     }
 
@@ -313,4 +314,11 @@
         mSnapshotView2.setDimAlpha(amount);
         mDigitalWellBeingToast2.setBannerColorTint(tintColor, amount);
     }
+
+    @Override
+    protected void applyThumbnailSplashAlpha() {
+        super.applyThumbnailSplashAlpha();
+        mSnapshotView2.setSplashAlpha(
+                Utilities.mapToRange(mOverviewProgress, 0f, 1f, 1f, 0f, LINEAR));
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 306ebd7..a736583 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -39,6 +39,7 @@
 import com.android.launcher3.popup.QuickstepSystemShortcut;
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statemanager.StateManager.StateListener;
+import com.android.launcher3.util.PendingSplitSelectInfo;
 import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.quickstep.LauncherActivityInterface;
 import com.android.quickstep.util.SplitSelectStateController;
@@ -89,6 +90,21 @@
     }
 
     @Override
+    public void onTaskIconChanged(int taskId) {
+        // If Launcher needs to return to split select state, do it now, after the icon has updated.
+        if (mActivity.hasPendingSplitSelectInfo()) {
+            PendingSplitSelectInfo recoveryData = mActivity.getPendingSplitSelectInfo();
+            if (recoveryData.getStagedTaskId() == taskId) {
+                initiateSplitSelect(
+                        getTaskViewByTaskId(recoveryData.getStagedTaskId()),
+                        recoveryData.getStagePosition()
+                );
+                mActivity.finishSplitSelectRecovery();
+            }
+        }
+    }
+
+    @Override
     public void reset() {
         super.reset();
 
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index 1c4e497..c178175 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,7 +31,6 @@
 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;
@@ -80,8 +77,9 @@
     private static final int INDEX_VISIBILITY_ALPHA = 1;
     private static final int INDEX_FULLSCREEN_ALPHA = 2;
     private static final int INDEX_HIDDEN_FLAGS_ALPHA = 3;
+    private static final int INDEX_SHARE_TARGET_ALPHA = 4;
 
-    private final MultiValueAlpha mMultiValueAlpha;
+    private MultiValueAlpha mMultiValueAlpha;
     private Button mSplitButton;
 
     @ActionsHiddenFlags
@@ -95,7 +93,6 @@
 
     @Nullable
     protected DeviceProfile mDp;
-    private final Rect mTaskSize = new Rect();
 
     public OverviewActionsView(Context context) {
         this(context, null);
@@ -107,13 +104,14 @@
 
     public OverviewActionsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr, 0);
-        mMultiValueAlpha = new MultiValueAlpha(this, 5);
-        mMultiValueAlpha.setUpdateVisibility(true);
     }
 
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
+        mMultiValueAlpha = new MultiValueAlpha(findViewById(R.id.action_buttons), 5);
+        mMultiValueAlpha.setUpdateVisibility(true);
+
         findViewById(R.id.action_screenshot).setOnClickListener(this);
 
         mSplitButton = findViewById(R.id.action_split);
@@ -195,6 +193,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 +204,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,33 +238,22 @@
             return 0;
         }
 
-        if (mDp.isVerticalBarLayout()) {
-            return mDp.getInsets().bottom;
-        }
-
         if (!mDp.isGestureMode && mDp.isTaskbarPresent) {
             return mDp.getOverviewActionsClaimedSpaceBelow();
         }
 
         // Align to bottom of task Rect.
-        return mDp.heightPx - mTaskSize.bottom - mDp.overviewActionsTopMarginPx
+        return mDp.heightPx - mDp.overviewTaskRect.bottom - mDp.overviewActionsTopMarginPx
                 - mDp.overviewActionsHeight;
     }
 
     /**
-     * Updates device profile and task size for this view to draw with.
+     * Updates device profile for this view to draw with.
      */
-    public void updateDimension(DeviceProfile dp, Rect taskSize) {
+    public void updateDimension(DeviceProfile dp) {
         mDp = dp;
-        mTaskSize.set(taskSize);
         updateVerticalMargin(DisplayController.getNavigationMode(getContext()));
 
-        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
-                dp.isVerticalBarLayout() ? 0 : dp.overviewActionsButtonSpacing,
-                ViewGroup.LayoutParams.MATCH_PARENT);
-        params.weight = dp.isVerticalBarLayout() ? 1 : 0;
-        findViewById(R.id.action_split_space).setLayoutParams(params);
-
         requestLayout();
 
         mSplitButton.setCompoundDrawablesWithIntrinsicBounds(
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 2360396..dc886f6 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -34,6 +34,7 @@
 import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.launcher3.anim.Interpolators.ACCEL_0_75;
 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
@@ -140,8 +141,8 @@
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.ResourceBasedOverride.Overrides;
 import com.android.launcher3.util.RunnableList;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
 import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
-import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.TranslateEdgeEffect;
 import com.android.launcher3.util.ViewPool;
@@ -151,7 +152,6 @@
 import com.android.quickstep.RecentsAnimationController;
 import com.android.quickstep.RecentsAnimationTargets;
 import com.android.quickstep.RecentsModel;
-import com.android.quickstep.RecentsModel.TaskVisualsChangeListener;
 import com.android.quickstep.RemoteAnimationTargets;
 import com.android.quickstep.RemoteTargetGluer;
 import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
@@ -169,6 +169,7 @@
 import com.android.quickstep.util.SplitSelectStateController;
 import com.android.quickstep.util.SurfaceTransactionApplier;
 import com.android.quickstep.util.TaskViewSimulator;
+import com.android.quickstep.util.TaskVisualsChangeListener;
 import com.android.quickstep.util.TransformParams;
 import com.android.quickstep.util.VibratorWrapper;
 import com.android.systemui.plugins.ResourceProvider;
@@ -362,6 +363,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 +380,23 @@
                 }
             };
 
+    /**
+     * Progress to and from the OVERVIEW state, where being in OverviewState has a value of 1, and
+     * being in any other LauncherState has a value of 0.
+     */
+    public static final FloatProperty<RecentsView> OVERVIEW_PROGRESS =
+            new FloatProperty<RecentsView>("overviewProgress") {
+                @Override
+                public void setValue(RecentsView view, float overviewProgress) {
+                    view.setOverviewProgress(overviewProgress);
+                }
+
+                @Override
+                public Float get(RecentsView view) {
+                    return view.mOverviewProgress;
+                }
+            };
+
     // OverScroll constants
     private static final int OVERSCROLL_PAGE_SNAP_ANIMATION_DURATION = 270;
 
@@ -406,9 +428,6 @@
 
     @Nullable
     protected RemoteTargetHandle[] mRemoteTargetHandles;
-    protected final Rect mLastComputedTaskSize = new Rect();
-    protected final Rect mLastComputedGridSize = new Rect();
-    protected final Rect mLastComputedGridTaskSize = new Rect();
     // How much a task that is directly offscreen will be pushed out due to RecentsView scale/pivot.
     @Nullable
     protected Float mLastComputedTaskStartPushOutDistance = null;
@@ -465,6 +484,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 mOverviewProgress = 0;
     private boolean mShowAsGridLastOnLayout = false;
     private final IntSet mTopRowIdSet = new IntSet();
 
@@ -624,12 +644,14 @@
     @Nullable
     private View mSecondSplitHiddenView;
     @Nullable
-    private StagedSplitBounds mSplitBoundsConfig;
+    private SplitBounds mSplitBoundsConfig;
     private final Toast mSplitToast = Toast.makeText(getContext(),
             R.string.toast_split_select_app, Toast.LENGTH_SHORT);
     private final Toast mSplitUnsupportedToast = Toast.makeText(getContext(),
             R.string.toast_split_app_unsupported, Toast.LENGTH_SHORT);
 
+    private SplitInstructionsView mSplitInstructionsView;
+
     @Nullable
     private QuickstepSystemShortcut.SplitSelectSource mSplitSelectSource;
 
@@ -1114,7 +1136,8 @@
         int targetScroll = getScrollForPage(indexOfChild(getFocusedTaskView()));
         if (!isClearAllHidden) {
             int clearAllWidth = mOrientationHandler.getPrimarySize(mClearAllButton);
-            int taskGridHorizontalDiff = mLastComputedTaskSize.right - mLastComputedGridSize.right;
+            int taskGridHorizontalDiff = mActivity.getDeviceProfile().overviewTaskRect.right
+                    - mActivity.getDeviceProfile().overviewGridRect.right;
             int clearAllFocusScrollDiff =  taskGridHorizontalDiff - clearAllWidth;
             targetScroll += mIsRtl ? clearAllFocusScrollDiff : -clearAllFocusScrollDiff;
         }
@@ -1445,11 +1468,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);
             }
@@ -1653,7 +1676,7 @@
 
         // Update RecentsView and TaskView's DeviceProfile dependent layout.
         updateOrientationHandler();
-        mActionsView.updateDimension(dp, mLastComputedTaskSize);
+        mActionsView.updateDimension(dp);
     }
 
     private void updateOrientationHandler() {
@@ -1709,7 +1732,7 @@
     // Update task size and padding that are dependent on DeviceProfile and insets.
     private void updateSizeAndPadding() {
         DeviceProfile dp = mActivity.getDeviceProfile();
-        getTaskSize(mTempRect);
+        mTempRect.set(dp.overviewTaskRect);
         mTaskWidth = mTempRect.width();
         mTaskHeight = mTempRect.height();
 
@@ -1718,14 +1741,9 @@
                 dp.widthPx - mInsets.right - mTempRect.right,
                 dp.heightPx - mInsets.bottom - mTempRect.bottom);
 
-        mSizeStrategy.calculateGridSize(mActivity.getDeviceProfile(),
-                mLastComputedGridSize);
-        mSizeStrategy.calculateGridTaskSize(mActivity, mActivity.getDeviceProfile(),
-                mLastComputedGridTaskSize, mOrientationHandler);
-
-        mTaskGridVerticalDiff = mLastComputedGridTaskSize.top - mLastComputedTaskSize.top;
+        mTaskGridVerticalDiff = dp.getOverviewGridTaskRect(mIsRtl).top - dp.overviewTaskRect.top;
         mTopBottomRowHeightDiff =
-                mLastComputedGridTaskSize.height() + dp.overviewTaskThumbnailTopMarginPx
+                dp.overviewGridTaskDimension.y + dp.overviewTaskThumbnailTopMarginPx
                         + dp.overviewRowSpacing;
 
         // Force TaskView to update size from thumbnail
@@ -1767,34 +1785,6 @@
         updateGridProperties(isTaskDismissal);
     }
 
-    public void getTaskSize(Rect outRect) {
-        mSizeStrategy.calculateTaskSize(mActivity, mActivity.getDeviceProfile(), outRect);
-        mLastComputedTaskSize.set(outRect);
-    }
-
-    /**
-     * Returns the size of task selected to enter modal state.
-     */
-    public Point getSelectedTaskSize() {
-        mSizeStrategy.calculateTaskSize(mActivity, mActivity.getDeviceProfile(),
-                mTempRect);
-        return new Point(mTempRect.width(), mTempRect.height());
-    }
-
-    /** Gets the last computed task size */
-    public Rect getLastComputedTaskSize() {
-        return mLastComputedTaskSize;
-    }
-
-    public Rect getLastComputedGridTaskSize() {
-        return mLastComputedGridTaskSize;
-    }
-
-    /** Gets the task size for modal state. */
-    public void getModalTaskSize(Rect outRect) {
-        mSizeStrategy.calculateModalTaskSize(mActivity, mActivity.getDeviceProfile(), outRect);
-    }
-
     @Override
     protected boolean computeScrollHelper() {
         boolean scrolling = super.computeScrollHelper();
@@ -2209,6 +2199,11 @@
                 }
             }
         }
+        if (animatorSet == null) {
+            setOverviewProgress(1);
+        } else {
+            animatorSet.play(ObjectAnimator.ofFloat(this, OVERVIEW_PROGRESS, 1));
+        }
     }
 
     /**
@@ -2300,7 +2295,7 @@
         boolean runningTaskTileHidden = mRunningTaskTileHidden;
         setCurrentTask(runningTaskViewId);
         mFocusedTaskViewId = runningTaskViewId;
-        setCurrentPage(getRunningTaskIndex());
+        runOnPageScrollsInitialized(() -> setCurrentPage(getRunningTaskIndex()));
         setRunningTaskViewShowScreenshot(false);
         setRunningTaskHidden(runningTaskTileHidden);
         // Update task size after setting current task.
@@ -2422,7 +2417,8 @@
             return;
         }
 
-        int taskTopMargin = mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx;
+        DeviceProfile dp = mActivity.getDeviceProfile();
+        int taskTopMargin = dp.overviewTaskThumbnailTopMarginPx;
 
         int topRowWidth = 0;
         int bottomRowWidth = 0;
@@ -2465,7 +2461,7 @@
                 gridTranslations[i] += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing;
 
                 // Center view vertically in case it's from different orientation.
-                taskView.setGridTranslationY((mLastComputedTaskSize.height() + taskTopMargin
+                taskView.setGridTranslationY((dp.overviewTaskRect.height() + taskTopMargin
                         - taskView.getLayoutParams().height) / 2f);
 
                 if (taskView == snappedTaskView) {
@@ -2581,11 +2577,11 @@
         // accordingly. Update longRowWidth if ClearAllButton has been moved.
         float clearAllShortTotalCompensation = 0;
         int longRowWidth = Math.max(topRowWidth, bottomRowWidth);
-        if (longRowWidth < mLastComputedGridSize.width()) {
-            float shortTotalCompensation = mLastComputedGridSize.width() - longRowWidth;
+        if (longRowWidth < dp.overviewGridRect.width()) {
+            float shortTotalCompensation = dp.overviewGridRect.width() - longRowWidth;
             clearAllShortTotalCompensation =
                     mIsRtl ? -shortTotalCompensation : shortTotalCompensation;
-            longRowWidth = mLastComputedGridSize.width();
+            longRowWidth = dp.overviewGridRect.width();
         }
 
         float clearAllTotalTranslationX =
@@ -2602,9 +2598,8 @@
         if (snappedTaskView != null) {
             int distanceFromClearAll = longRowWidth - snappedTaskRowWidth + mPageSpacing;
             // ClearAllButton should be off screen when snapped task is in its snapped position.
-            int minimumDistance =
-                    mTaskWidth - snappedTaskView.getLayoutParams().width
-                            + (mLastComputedGridSize.width() - mTaskWidth) / 2;
+            int minimumDistance = mTaskWidth - snappedTaskView.getLayoutParams().width
+                    + (dp.overviewGridRect.width() - mTaskWidth) / 2;
             if (distanceFromClearAll < minimumDistance) {
                 int distanceDifference = minimumDistance - distanceFromClearAll;
                 snappedTaskGridTranslationX += mIsRtl ? distanceDifference : -distanceDifference;
@@ -2619,9 +2614,9 @@
 
         mClearAllButton.setGridTranslationPrimary(
                 clearAllTotalTranslationX - snappedTaskGridTranslationX);
-        mClearAllButton.setGridScrollOffset(
-                mIsRtl ? mLastComputedTaskSize.left - mLastComputedGridSize.left
-                        : mLastComputedTaskSize.right - mLastComputedGridSize.right);
+        mClearAllButton.setGridScrollOffset(mIsRtl
+                ? dp.overviewTaskRect.left - dp.overviewGridRect.left
+                : dp.overviewTaskRect.right - dp.overviewGridRect.right);
 
         setGridProgress(mGridProgress);
     }
@@ -2658,6 +2653,18 @@
         mClearAllButton.setGridProgress(gridProgress);
     }
 
+    private void setOverviewProgress(float overviewProgress) {
+        int taskCount = getTaskViewCount();
+        if (taskCount == 0) {
+            return;
+        }
+
+        mOverviewProgress = overviewProgress;
+        for (int i = 0; i < taskCount; i++) {
+            requireTaskViewAt(i).setOverviewProgress(overviewProgress);
+        }
+    }
+
     private void enableLayoutTransitions() {
         if (mLayoutTransition == null) {
             mLayoutTransition = new LayoutTransition();
@@ -2764,11 +2771,15 @@
             mFirstFloatingTaskView.addAnimation(anim, startingTaskRect, mTempRect,
                     false /* fadeWithThumbnail */, true /* isStagedTask */);
         }
+
+        mSplitInstructionsView = SplitInstructionsView.getSplitInstructionsView(mActivity);
+        mSplitInstructionsView.setAlpha(0);
+        anim.addFloat(mSplitInstructionsView, SplitInstructionsView.ALPHA_FLOAT, 0, 1, ACCEL);
+
         InteractionJankMonitorWrapper.begin(this,
                 InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER, "First tile selected");
         anim.addEndListener(success -> {
             if (success) {
-                mSplitToast.show();
                 InteractionJankMonitorWrapper.end(
                         InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
             } else {
@@ -2858,8 +2869,8 @@
         boolean closeGapBetweenClearAll = false;
         boolean isClearAllHidden = isClearAllHidden();
         boolean snapToLastTask = false;
-        boolean isLandscapeSplit =
-                mActivity.getDeviceProfile().isLandscape && isSplitSelectionActive();
+        DeviceProfile dp = mActivity.getDeviceProfile();
+        boolean isLandscapeSplit = dp.isLandscape && isSplitSelectionActive();
         boolean isSplitPlaceholderFirstInGrid = isSplitPlaceholderFirstInGrid();
         boolean isSplitPlaceholderLastInGrid = isSplitPlaceholderLastInGrid();
         TaskView lastGridTaskView = showAsGrid ? getLastGridTaskView() : null;
@@ -3050,7 +3061,7 @@
                         dismissTranslationInterpolationEnd);
                 if (taskView == nextFocusedTaskView) {
                     // Enlarge the task to be focused next, and translate into focus position.
-                    float scale = mTaskWidth / (float) mLastComputedGridTaskSize.width();
+                    float scale = mTaskWidth / (float) dp.overviewGridTaskDimension.x;
                     anim.setFloat(taskView, TaskView.SNAPSHOT_SCALE, scale,
                             clampToProgress(LINEAR, animationStartProgress,
                                     dismissTranslationInterpolationEnd));
@@ -3306,7 +3317,7 @@
                         // Update various scroll-dependent UI.
                         dispatchScrollChanged();
                         updateActionsViewFocusedScroll();
-                        if (isClearAllHidden() && !mActivity.getDeviceProfile().isTablet) {
+                        if (isClearAllHidden() && !dp.isTablet) {
                             mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING,
                                     false);
                         }
@@ -3704,7 +3715,6 @@
         updateEmptyStateUi(changed);
 
         // Update the pivots such that when the task is scaled, it fills the full page
-        getTaskSize(mTempRect);
         updatePivots();
         setTaskModalness(mTaskModalness);
         mLastComputedTaskStartPushOutDistance = null;
@@ -3718,12 +3728,13 @@
     }
 
     private void updatePivots() {
+        DeviceProfile dp = mActivity.getDeviceProfile();
         if (mOverviewSelectEnabled) {
-            setPivotX(mLastComputedTaskSize.centerX());
-            setPivotY(mLastComputedTaskSize.bottom);
+            setPivotX(dp.overviewTaskRect.centerX());
+            setPivotY(dp.overviewTaskRect.bottom);
         } else {
-            getPagedViewOrientedState().getFullScreenScaleAndPivot(mTempRect,
-                    mActivity.getDeviceProfile(), mTempPointF);
+            getPagedViewOrientedState().getFullScreenScaleAndPivot(dp.overviewTaskRect, dp,
+                    mTempPointF);
             setPivotX(mTempPointF.x);
             setPivotY(mTempPointF.y);
         }
@@ -3996,8 +4007,8 @@
     }
 
     public void initiateSplitSelect(TaskView taskView) {
-        int defaultSplitPosition = mOrientationHandler
-                .getDefaultSplitPosition(mActivity.getDeviceProfile());
+        int defaultSplitPosition =
+                mOrientationHandler.getDefaultSplitPosition(mActivity.getDeviceProfile());
         initiateSplitSelect(taskView, defaultSplitPosition);
     }
 
@@ -4007,7 +4018,8 @@
                 stagePosition);
         mSplitHiddenTaskViewIndex = indexOfChild(taskView);
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            finishRecentsAnimation(true, null);
+            finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
+                    null /* onFinishComplete */);
         }
     }
 
@@ -4091,6 +4103,10 @@
         mSecondSplitHiddenView.setVisibility(INVISIBLE);
         InteractionJankMonitorWrapper.begin(this,
                 InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER, "Second tile selected");
+
+        // Fade out all other views underneath placeholders
+        ObjectAnimator tvFade = ObjectAnimator.ofFloat(this, RecentsView.CONTENT_ALPHA,1, 0);
+        pendingAnimation.add(tvFade, DEACCEL_2, SpringProperty.DEFAULT);
         pendingAnimation.buildAnim().start();
         return true;
     }
@@ -4099,12 +4115,16 @@
     @SuppressLint("WrongCall")
     protected void resetFromSplitSelectionState() {
         if (mSplitSelectSource != null || mSplitHiddenTaskViewIndex != -1) {
+            if (mSplitInstructionsView != null) {
+                mActivity.getDragLayer().removeView(mSplitInstructionsView);
+                mSplitInstructionsView = null;
+            }
             if (mFirstFloatingTaskView != null) {
-                mActivity.getRootView().removeView(mFirstFloatingTaskView);
+                mActivity.getDragLayer().removeView(mFirstFloatingTaskView);
                 mFirstFloatingTaskView = null;
             }
             if (mSecondFloatingTaskView != null) {
-                mActivity.getRootView().removeView(mSecondFloatingTaskView);
+                mActivity.getDragLayer().removeView(mSecondFloatingTaskView);
                 mSecondFloatingTaskView = null;
                 mSecondSplitHiddenView.setVisibility(VISIBLE);
                 mSecondSplitHiddenView = null;
@@ -4164,6 +4184,10 @@
         taskViewsFloat.first.set(this, getSplitSelectTranslation());
         taskViewsFloat.second.set(this, 0f);
 
+        if (mSplitInstructionsView != null) {
+            mSplitInstructionsView.ensureProperRotation();
+        }
+
         applySplitPrimaryScrollOffset();
     }
 
@@ -4281,6 +4305,7 @@
                         properties));
             }
         }
+        anim.play(ObjectAnimator.ofFloat(recentsView, OVERVIEW_PROGRESS, 1, 0));
         return anim;
     }
 
@@ -4288,9 +4313,9 @@
      * Returns the scale up required on the view, so that it coves the screen completely
      */
     public float getMaxScaleForFullScreen() {
-        getTaskSize(mTempRect);
-        return getPagedViewOrientedState().getFullScreenScaleAndPivot(
-                mTempRect, mActivity.getDeviceProfile(), mTempPointF);
+        DeviceProfile dp = mActivity.getDeviceProfile();
+        return getPagedViewOrientedState().getFullScreenScaleAndPivot(dp.overviewTaskRect,
+                dp, mTempPointF);
     }
 
     public PendingAnimation createTaskLaunchAnimation(
@@ -4340,6 +4365,8 @@
                     BACKGROUND_APP.getDepth(mActivity));
             anim.play(depthAnimator);
         }
+        anim.play(ObjectAnimator.ofFloat(this, OVERVIEW_PROGRESS, 1f, 0f));
+
         anim.play(progressAnim);
         anim.setInterpolator(interpolator);
 
@@ -4473,7 +4500,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 +4550,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 +4570,7 @@
                     new PictureInPictureSurfaceTransaction.Builder()
                             .setAlpha(0f)
                             .build();
+            tx.setShouldDisableCanAffectSystemUiFlags(false);
             int[] taskIds = TopTaskTracker.INSTANCE.get(getContext()).getRunningSplitTaskIds();
             for (int taskId : taskIds) {
                 mRecentsAnimationController.setFinishTaskTransaction(taskId,
@@ -4800,17 +4822,15 @@
         int lastGridTaskViewPosition =
                 getPositionInRow(lastGridTaskView, topRowIdArray, bottomRowIdArray);
         int taskViewPosition = getPositionInRow(taskView, topRowIdArray, bottomRowIdArray);
-        int gridTaskSizeAndSpacing = mLastComputedGridTaskSize.width() + mPageSpacing;
+        DeviceProfile dp = mActivity.getDeviceProfile();
+        Rect gridTaskRect = dp.getOverviewGridTaskRect(mIsRtl);
+        int gridTaskSizeAndSpacing = gridTaskRect.width() + mPageSpacing;
         int positionDiff = gridTaskSizeAndSpacing * (lastGridTaskViewPosition - taskViewPosition);
 
-        int lastTaskEnd = (mIsRtl
-                ? mLastComputedGridSize.left
-                : mLastComputedGridSize.right)
+        int lastTaskEnd = (mIsRtl ? dp.overviewGridRect.left : dp.overviewGridRect.right)
                 + (mIsRtl ? mPageSpacing : -mPageSpacing);
         int taskEnd = lastTaskEnd + (mIsRtl ? positionDiff : -positionDiff);
-        int normalTaskEnd = mIsRtl
-                ? mLastComputedGridTaskSize.left
-                : mLastComputedGridTaskSize.right;
+        int normalTaskEnd = mIsRtl ? gridTaskRect.left : gridTaskRect.right;
         return taskEnd - normalTaskEnd;
     }
 
@@ -4859,10 +4879,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));
         }
     }
 
@@ -5116,14 +5136,12 @@
             while ((taskView == null || isTaskViewFullyVisible(taskView)) && targetPage - 1 >= 0) {
                 taskView = getTaskViewAt(--targetPage);
             }
+            DeviceProfile dp = mActivity.getDeviceProfile();
+            Rect gridTaskRect = dp.getOverviewGridTaskRect(mIsRtl);
             // Target a scroll where targetPage is on left of screen but still fully visible.
-            int lastTaskEnd = (mIsRtl
-                    ? mLastComputedGridSize.left
-                    : mLastComputedGridSize.right)
+            int lastTaskEnd = (mIsRtl ? dp.overviewGridRect.left : dp.overviewGridRect.right)
                     + (mIsRtl ? mPageSpacing : -mPageSpacing);
-            int normalTaskEnd = mIsRtl
-                    ? mLastComputedGridTaskSize.left
-                    : mLastComputedGridTaskSize.right;
+            int normalTaskEnd = mIsRtl ? gridTaskRect.left : gridTaskRect.right;
             int targetScroll = getScrollForPage(targetPage) + normalTaskEnd - lastTaskEnd;
             // Find a page that is close to targetScroll while not over it.
             while (targetPage - 1 >= 0
diff --git a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
new file mode 100644
index 0000000..7d94505
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.views;
+
+import static com.android.launcher3.util.DisplayController.NavigationMode.THREE_BUTTONS;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.FloatProperty;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.R;
+import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.util.DisplayController;
+
+/**
+ * A rounded rectangular component containing a single TextView.
+ * Appears when a split is in progress, and tells the user to select a second app to initiate
+ * splitscreen.
+ *
+ * Appears and disappears concurrently with a FloatingTaskView.
+ */
+public class SplitInstructionsView extends FrameLayout {
+    private final StatefulActivity mLauncher;
+
+    public static final FloatProperty<SplitInstructionsView> ALPHA_FLOAT =
+            new FloatProperty<SplitInstructionsView>("SplitInstructionsAlpha") {
+                @Override
+                public void setValue(SplitInstructionsView splitInstructionsView, float v) {
+                    splitInstructionsView.setVisibility(v != 0 ? VISIBLE : GONE);
+                    splitInstructionsView.setAlpha(v);
+                }
+
+                @Override
+                public Float get(SplitInstructionsView splitInstructionsView) {
+                    return splitInstructionsView.getAlpha();
+                }
+            };
+
+    public SplitInstructionsView(Context context) {
+        this(context, null);
+    }
+
+    public SplitInstructionsView(Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public SplitInstructionsView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mLauncher = (StatefulActivity) context;
+    }
+
+    static SplitInstructionsView getSplitInstructionsView(StatefulActivity launcher) {
+        ViewGroup dragLayer = launcher.getDragLayer();
+        final SplitInstructionsView splitInstructionsView =
+                (SplitInstructionsView) launcher.getLayoutInflater().inflate(
+                        R.layout.split_instructions_view,
+                        dragLayer,
+                        false
+                );
+
+        dragLayer.addView(splitInstructionsView);
+        return splitInstructionsView;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        ensureProperRotation();
+    }
+
+    void ensureProperRotation() {
+        ((RecentsView) mLauncher.getOverviewPanel()).getPagedOrientationHandler()
+                .setSplitInstructionsParams(
+                        this,
+                        mLauncher.getDeviceProfile(),
+                        getMeasuredHeight(),
+                        getMeasuredWidth(),
+                        getThreeButtonNavShift()
+                );
+    }
+
+    // In some cases, when user is using 3-button nav, there isn't enough room for both the
+    // 3-button nav and a centered SplitInstructionsView. This function will return an int that will
+    // be used to shift the SplitInstructionsView over a bit so that everything looks well-spaced.
+    // In many cases, this will return 0, since we don't need to shift it away from the center.
+    int getThreeButtonNavShift() {
+        DeviceProfile dp = mLauncher.getDeviceProfile();
+        if ((DisplayController.getNavigationMode(getContext()) == THREE_BUTTONS)
+                && ((dp.isTwoPanels) || (dp.isTablet && !dp.isLandscape))) {
+            int navButtonWidth = getResources().getDimensionPixelSize(
+                    R.dimen.taskbar_nav_buttons_size);
+            int extraMargin = getResources().getDimensionPixelSize(
+                    R.dimen.taskbar_contextual_button_margin);
+            // Explanation: The 3-button nav for non-phones sits on one side of the screen, taking
+            // up 3 buttons + a side margin worth of space. Our splitInstructionsView starts in the
+            // center of the screen and we want to center it in the remaining space, therefore we
+            // want to shift it over by half the 3-button layout's width.
+            // If the user is using an RtL layout, we shift it the opposite way.
+            return -((3 * navButtonWidth + extraMargin) / 2) * (isLayoutRtl() ? -1 : 1);
+        } else {
+            return 0;
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index 3803f1b..c1711d1 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -16,7 +16,6 @@
 
 package com.android.quickstep.views;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
 import static com.android.quickstep.views.TaskThumbnailView.DIM_ALPHA;
@@ -232,8 +231,7 @@
     private void addMenuOptions(TaskIdAttributeContainer taskContainer) {
         mTaskName.setText(TaskUtils.getTitle(getContext(), taskContainer.getTask()));
         mTaskName.setOnClickListener(v -> close(true));
-        TaskOverlayFactory.getEnabledShortcuts(mTaskView, mActivity.getDeviceProfile(),
-                taskContainer)
+        TaskOverlayFactory.getEnabledShortcuts(mTaskView, taskContainer)
                 .forEach(this::addMenuOption);
     }
 
@@ -245,17 +243,9 @@
         LayoutParams lp = (LayoutParams) menuOptionView.getLayoutParams();
         mTaskView.getPagedOrientationHandler().setLayoutParamsForTaskMenuOptionItem(lp,
                 menuOptionView, mActivity.getDeviceProfile());
-        menuOptionView.setOnClickListener(view -> {
-            if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-                RecentsView recentsView = mTaskView.getRecentsView();
-                recentsView.switchToScreenshot(null,
-                        () -> recentsView.finishRecentsAnimation(true /* toRecents */,
-                                false /* shouldPip */,
-                                () -> menuOption.onClick(view)));
-            } else {
-                menuOption.onClick(view);
-            }
-        });
+        // Set an onClick listener on each menu option. The onClick method is responsible for
+        // ending LiveTile mode on the thumbnail if needed.
+        menuOptionView.setOnClickListener(menuOption::onClick);
         mOptionLayout.addView(menuOptionView);
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
index 06a5793..b586ac3 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
@@ -164,7 +164,7 @@
     private fun addMenuOptions() {
         // Add the options
         TaskOverlayFactory
-            .getEnabledShortcuts(taskView, mActivityContext.deviceProfile, taskContainer)
+            .getEnabledShortcuts(taskView, taskContainer)
             .forEach { this.addMenuOption(it) }
 
         // Add the spaces between items
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index d8120ff..f4b3d98 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -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..ba56af1 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;
@@ -43,7 +43,6 @@
 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;
@@ -57,7 +56,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 +65,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
@@ -89,7 +86,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;
@@ -139,41 +135,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 +323,6 @@
                 }
             };
 
-    private final TaskOutlineProvider mOutlineProvider;
-
     @Nullable
     protected Task mTask;
     protected TaskThumbnailView mSnapshotView;
@@ -371,6 +330,7 @@
     protected final DigitalWellBeingToast mDigitalWellBeingToast;
     private float mFullscreenProgress;
     private float mGridProgress;
+    protected float mOverviewProgress;
     private float mNonGridScale = 1;
     private float mDismissScale = 1;
     protected final FullscreenDrawParams mCurrentFullscreenParams;
@@ -430,7 +390,6 @@
 
     private boolean mIsClickableAsLiveTile = true;
 
-
     public TaskView(Context context) {
         this(context, null);
     }
@@ -446,10 +405,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) {
@@ -698,6 +653,9 @@
             if (freezeTaskList) {
                 ActivityOptionsCompat.setFreezeRecentTasksList(opts);
             }
+            // 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)) {
@@ -726,10 +684,6 @@
                 return;
             }
 
-            // Reset the minimized state since we force-toggled the minimized state when entering
-            // overview, but never actually finished the recents animation
-            SystemUiProxy.INSTANCE.get(getContext()).setSplitScreenMinimized(false);
-
             mIsClickableAsLiveTile = false;
             RemoteAnimationTargets targets;
             if (remoteTargetHandles.length == 1) {
@@ -1102,6 +1056,21 @@
         return scale;
     }
 
+    /**
+     * Updates progress of task view for entering/exiting overview on swipe up/down.
+     *
+     * <p>Updates the alpha of any splash screen over the thumbnail if it exists.
+     */
+    public void setOverviewProgress(float overviewProgress) {
+        mOverviewProgress = overviewProgress;
+        applyThumbnailSplashAlpha();
+    }
+
+    protected void applyThumbnailSplashAlpha() {
+        mSnapshotView.setSplashAlpha(
+                Utilities.mapToRange(mOverviewProgress, 0f, 1f, 1f, 0f, LINEAR));
+    }
+
     private void setSplitSelectTranslationX(float x) {
         mSplitSelectTranslationX = x;
         applyTranslationX();
@@ -1289,33 +1258,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 +1285,7 @@
                 continue;
             }
             for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this,
-                    mActivity.getDeviceProfile(), taskContainer)) {
+                    taskContainer)) {
                 info.addAction(s.createAccessibilityAction(context));
             }
         }
@@ -1381,7 +1323,7 @@
                 continue;
             }
             for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this,
-                    mActivity.getDeviceProfile(), taskContainer)) {
+                    taskContainer)) {
                 if (s.hasHandlerForAction(action)) {
                     s.onClick(this);
                     return true;
@@ -1421,11 +1363,6 @@
         mSnapshotView.getTaskOverlay().setFullscreenProgress(progress);
 
         updateSnapshotRadius();
-
-        mOutlineProvider.updateParams(
-                mCurrentFullscreenParams,
-                mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx);
-        invalidateOutline();
     }
 
     protected void updateSnapshotRadius() {
@@ -1454,9 +1391,8 @@
         DeviceProfile deviceProfile = mActivity.getDeviceProfile();
         if (deviceProfile.isTablet) {
             final int thumbnailPadding = deviceProfile.overviewTaskThumbnailTopMarginPx;
-            final Rect lastComputedTaskSize = getRecentsView().getLastComputedTaskSize();
-            final int taskWidth = lastComputedTaskSize.width();
-            final int taskHeight = lastComputedTaskSize.height();
+            final int taskWidth = deviceProfile.overviewTaskRect.width();
+            final int taskHeight = deviceProfile.overviewTaskRect.height();
 
             int boxWidth;
             int boxHeight;
@@ -1467,10 +1403,9 @@
                 boxWidth = taskWidth;
                 boxHeight = taskHeight;
             } else {
-                // Otherwise task is in grid, and should use lastComputedGridTaskSize.
-                Rect lastComputedGridTaskSize = getRecentsView().getLastComputedGridTaskSize();
-                boxWidth = lastComputedGridTaskSize.width();
-                boxHeight = lastComputedGridTaskSize.height();
+                // Otherwise task is in grid.
+                boxWidth = deviceProfile.overviewGridTaskDimension.x;
+                boxHeight = deviceProfile.overviewGridTaskDimension.y;
             }
 
             // Bound width/height to the box size.
@@ -1537,7 +1472,6 @@
     }
 
     public void initiateSplitSelect(SplitPositionOption splitPositionOption) {
-        AbstractFloatingView.closeOpenViews(mActivity, false, TYPE_TASK_MENU);
         getRecentsView().initiateSplitSelect(this, splitPositionOption.stagePosition);
     }
 
@@ -1584,17 +1518,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/DeviceProfilePhone3ButtonTest.kt b/quickstep/tests/src/com/android/quickstep/DeviceProfilePhone3ButtonTest.kt
new file mode 100644
index 0000000..588e346
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/DeviceProfilePhone3ButtonTest.kt
@@ -0,0 +1,596 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.DeviceProfileBaseTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for DeviceProfile for phone with 3-Button navigation.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceProfilePhone3ButtonTest : DeviceProfileBaseTest() {
+
+    lateinit var dp: DeviceProfile
+
+    @Before
+    fun before() {
+        initializeVarsForPhone(isGestureMode = false)
+        dp = newDP()
+    }
+
+    @Test
+    fun isScalableGrid() {
+        assertThat(dp.isScalableGrid).isTrue()
+    }
+
+    @Test
+    fun cellWidthPx() {
+        assertThat(dp.cellWidthPx).isEqualTo(265)
+    }
+
+    @Test
+    fun cellHeightPx() {
+        assertThat(dp.cellHeightPx).isEqualTo(343)
+    }
+
+    @Test
+    fun getCellSizeX() {
+        assertThat(dp.getCellSize().x).isEqualTo(265)
+    }
+
+    @Test
+    fun getCellSizeY() {
+        assertThat(dp.getCellSize().y).isEqualTo(552)
+    }
+
+    @Test
+    fun cellLayoutBorderSpacePxX() {
+        assertThat(dp.cellLayoutBorderSpacePx.x).isEqualTo(66)
+    }
+
+    @Test
+    fun cellLayoutBorderSpacePxY() {
+        assertThat(dp.cellLayoutBorderSpacePx.y).isEqualTo(66)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxLeft() {
+        assertThat(dp.cellLayoutPaddingPx.left).isEqualTo(38)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxTop() {
+        assertThat(dp.cellLayoutPaddingPx.top).isEqualTo(0)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxRight() {
+        assertThat(dp.cellLayoutPaddingPx.right).isEqualTo(38)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxBottom() {
+        assertThat(dp.cellLayoutPaddingPx.bottom).isEqualTo(38)
+    }
+
+    @Test
+    fun iconSizePx() {
+        assertThat(dp.iconSizePx).isEqualTo(196)
+    }
+
+    @Test
+    fun iconTextSizePx() {
+        assertThat(dp.iconTextSizePx).isEqualTo(49)
+    }
+
+    @Test
+    fun iconDrawablePaddingPx() {
+        assertThat(dp.iconDrawablePaddingPx).isEqualTo(25)
+    }
+
+    @Test
+    fun folderCellWidthPx() {
+        assertThat(dp.folderCellWidthPx).isEqualTo(265)
+    }
+
+    @Test
+    fun folderCellHeightPx() {
+        assertThat(dp.folderCellHeightPx).isEqualTo(343)
+    }
+
+    @Test
+    fun folderChildIconSizePx() {
+        assertThat(dp.folderChildIconSizePx).isEqualTo(196)
+    }
+
+    @Test
+    fun folderChildTextSizePx() {
+        assertThat(dp.folderChildTextSizePx).isEqualTo(49)
+    }
+
+    @Test
+    fun folderChildDrawablePaddingPx() {
+        assertThat(dp.folderChildDrawablePaddingPx).isEqualTo(27)
+    }
+
+    @Test
+    fun folderCellLayoutBorderSpaceOriginalPx() {
+        assertThat(dp.folderCellLayoutBorderSpaceOriginalPx).isEqualTo(0)
+    }
+
+    @Test
+    fun folderCellLayoutBorderSpacePxX() {
+        assertThat(dp.folderCellLayoutBorderSpacePx.x).isEqualTo(0)
+    }
+
+    @Test
+    fun folderCellLayoutBorderSpacePxY() {
+        assertThat(dp.folderCellLayoutBorderSpacePx.y).isEqualTo(0)
+    }
+
+    @Test
+    fun bottomSheetTopPadding() {
+        assertThat(dp.bottomSheetTopPadding).isEqualTo(38)
+    }
+
+    @Test
+    fun allAppsShiftRange() {
+        assertThat(dp.allAppsShiftRange).isEqualTo(1050)
+    }
+
+    @Test
+    fun allAppsTopPadding() {
+        assertThat(dp.allAppsTopPadding).isEqualTo(0)
+    }
+
+    @Test
+    fun allAppsIconSizePx() {
+        assertThat(dp.allAppsIconSizePx).isEqualTo(232)
+    }
+
+    @Test
+    fun allAppsIconTextSizePx() {
+        assertThat(dp.allAppsIconTextSizePx).isEqualTo(58)
+    }
+
+    @Test
+    fun allAppsIconDrawablePaddingPx() {
+        assertThat(dp.allAppsIconDrawablePaddingPx).isEqualTo(25)
+    }
+
+    @Test
+    fun allAppsCellHeightPx() {
+        assertThat(dp.allAppsCellHeightPx).isEqualTo(409)
+    }
+
+    @Test
+    fun allAppsCellWidthPx() {
+        assertThat(dp.allAppsCellWidthPx).isEqualTo(265)
+    }
+
+    @Test
+    fun allAppsBorderSpacePxX() {
+        assertThat(dp.allAppsBorderSpacePx.x).isEqualTo(66)
+    }
+
+    @Test
+    fun allAppsBorderSpacePxY() {
+        assertThat(dp.allAppsBorderSpacePx.y).isEqualTo(66)
+    }
+
+    @Test
+    fun numShownAllAppsColumns() {
+        assertThat(dp.numShownAllAppsColumns).isEqualTo(0)
+    }
+
+    @Test
+    fun allAppsLeftRightPadding() {
+        assertThat(dp.allAppsLeftRightPadding).isEqualTo(91)
+    }
+
+    @Test
+    fun allAppsLeftRightMargin() {
+        assertThat(dp.allAppsLeftRightMargin).isEqualTo(0)
+    }
+
+    @Test
+    fun hotseatBarSizePx() {
+        assertThat(dp.hotseatBarSizePx).isEqualTo(669)
+    }
+
+    @Test
+    fun hotseatCellHeightPx() {
+        assertThat(dp.hotseatCellHeightPx).isEqualTo(221)
+    }
+
+    @Test
+    fun hotseatBarBottomPaddingPx() {
+        assertThat(dp.hotseatBarBottomSpacePx).isEqualTo(168)
+    }
+
+    @Test
+    fun hotseatBarSidePaddingStartPx() {
+        assertThat(dp.hotseatBarSidePaddingStartPx).isEqualTo(0)
+    }
+
+    @Test
+    fun hotseatBarSidePaddingEndPx() {
+        assertThat(dp.hotseatBarSidePaddingEndPx).isEqualTo(0)
+    }
+
+    @Test
+    fun hotseatQsbSpace() {
+        assertThat(dp.hotseatQsbSpace).isEqualTo(126)
+    }
+
+    @Test
+    fun hotseatQsbHeight() {
+        assertThat(dp.hotseatQsbHeight).isEqualTo(221)
+    }
+
+    @Test
+    fun springLoadedHotseatBarTopMarginPx() {
+        assertThat(dp.springLoadedHotseatBarTopMarginPx).isEqualTo(266)
+    }
+
+    @Test
+    fun numShownHotseatIcons() {
+        assertThat(dp.numShownHotseatIcons).isEqualTo(4)
+    }
+
+    @Test
+    fun hotseatBorderSpace() {
+        assertThat(dp.hotseatBorderSpace).isEqualTo(135)
+    }
+
+    @Test
+    fun isQsbInline() {
+        assertThat(dp.isQsbInline).isEqualTo(false)
+    }
+
+    @Test
+    fun qsbWidth() {
+        assertThat(dp.qsbWidth).isEqualTo(1189)
+    }
+
+    @Test
+    fun isTaskbarPresent() {
+        assertThat(dp.isTaskbarPresent).isEqualTo(false)
+    }
+
+    @Test
+    fun isTaskbarPresentInApps() {
+        assertThat(dp.isTaskbarPresentInApps).isEqualTo(false)
+    }
+
+    @Test
+    fun taskbarSize() {
+        assertThat(dp.taskbarSize).isEqualTo(0)
+    }
+
+    @Test
+    fun desiredWorkspaceHorizontalMarginPx() {
+        assertThat(dp.desiredWorkspaceHorizontalMarginPx).isEqualTo(91)
+    }
+
+    @Test
+    fun workspacePaddingLeft() {
+        assertThat(dp.workspacePadding.left).isEqualTo(53)
+    }
+
+    @Test
+    fun workspacePaddingTop() {
+        assertThat(dp.workspacePadding.top).isEqualTo(0)
+    }
+
+    @Test
+    fun workspacePaddingRight() {
+        assertThat(dp.workspacePadding.right).isEqualTo(53)
+    }
+
+    @Test
+    fun workspacePaddingBottom() {
+        assertThat(dp.workspacePadding.bottom).isEqualTo(573)
+    }
+
+    @Test
+    fun iconScale() {
+        assertThat(dp.iconScale).isEqualTo(1)
+    }
+
+    @Test
+    fun cellScaleToFit() {
+        assertThat(dp.cellScaleToFit).isEqualTo(1.182266f)
+    }
+
+    @Test
+    fun workspaceTopPadding() {
+        assertThat(dp.workspaceTopPadding).isEqualTo(0)
+    }
+
+    @Test
+    fun workspaceBottomPadding() {
+        assertThat(dp.workspaceBottomPadding).isEqualTo(0)
+    }
+
+    @Test
+    fun overviewTaskMarginPx() {
+        assertThat(dp.overviewTaskMarginPx).isEqualTo(56)
+    }
+
+    @Test
+    fun overviewTaskMarginGridPx() {
+        assertThat(dp.overviewTaskMarginGridPx).isEqualTo(0)
+    }
+
+    @Test
+    fun overviewTaskIconSizePx() {
+        assertThat(dp.overviewTaskIconSizePx).isEqualTo(168)
+    }
+
+    @Test
+    fun overviewTaskIconDrawableSizePx() {
+        assertThat(dp.overviewTaskIconDrawableSizePx).isEqualTo(154)
+    }
+
+    @Test
+    fun overviewTaskIconDrawableSizeGridPx() {
+        assertThat(dp.overviewTaskIconDrawableSizeGridPx).isEqualTo(0)
+    }
+
+    @Test
+    fun overviewTaskThumbnailTopMarginPx() {
+        assertThat(dp.overviewTaskThumbnailTopMarginPx).isEqualTo(280)
+    }
+
+    @Test
+    fun overviewActionsTopMarginPx() {
+        assertThat(dp.overviewActionsTopMarginPx).isEqualTo(84)
+    }
+
+    @Test
+    fun overviewActionsHeight() {
+        assertThat(dp.overviewActionsHeight).isEqualTo(168)
+    }
+
+    @Test
+    fun overviewActionsButtonSpacing() {
+        assertThat(dp.overviewActionsButtonSpacing).isEqualTo(126)
+    }
+
+    @Test
+    fun overviewPageSpacing() {
+        assertThat(dp.overviewPageSpacing).isEqualTo(56)
+    }
+
+    @Test
+    fun overviewRowSpacing() {
+        assertThat(dp.overviewRowSpacing).isEqualTo(-112)
+    }
+
+    @Test
+    fun overviewGridSideMargin() {
+        assertThat(dp.overviewGridSideMargin).isEqualTo(0)
+    }
+
+    @Test
+    fun dropTargetBarTopMarginPx() {
+        assertThat(dp.dropTargetBarTopMarginPx).isEqualTo(112)
+    }
+
+    @Test
+    fun dropTargetBarSizePx() {
+        assertThat(dp.dropTargetBarSizePx).isEqualTo(196)
+    }
+
+    @Test
+    fun dropTargetBarBottomMarginPx() {
+        assertThat(dp.dropTargetBarBottomMarginPx).isEqualTo(56)
+    }
+
+    @Test
+    fun workspaceSpringLoadedMinNextPageVisiblePx() {
+        assertThat(dp.workspaceSpringLoadedMinNextPageVisiblePx).isEqualTo(84)
+    }
+
+    @Test
+    fun getWorkspaceSpringLoadScale() {
+        assertThat(dp.workspaceSpringLoadScale).isEqualTo(0.74417657f)
+    }
+
+    @Test
+    fun getCellLayoutHeight() {
+        assertThat(dp.cellLayoutHeight).isEqualTo(2447)
+    }
+
+    @Test
+    fun getCellLayoutWidth() {
+        assertThat(dp.cellLayoutWidth).isEqualTo(1334)
+    }
+
+    @Test
+    fun getPanelCount() {
+        assertThat(dp.panelCount).isEqualTo(1)
+    }
+
+    @Test
+    fun isVerticalBarLayout() {
+        assertThat(dp.isVerticalBarLayout).isEqualTo(false)
+    }
+
+    @Test
+    fun getCellLayoutSpringLoadShrunkTop() {
+        assertThat(dp.cellLayoutSpringLoadShrunkTop).isEqualTo(364)
+    }
+
+    @Test
+    fun getCellLayoutSpringLoadShrunkBottom() {
+        assertThat(dp.cellLayoutSpringLoadShrunkBottom).isEqualTo(2185)
+    }
+
+    @Test
+    fun getQsbOffsetY() {
+        assertThat(dp.qsbOffsetY).isEqualTo(147)
+    }
+
+    @Test
+    fun getTaskbarOffsetY() {
+        assertThat(dp.taskbarOffsetY).isEqualTo(558)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingLeft() {
+        assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(125)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingTop() {
+        assertThat(dp.getHotseatLayoutPadding(context).top).isEqualTo(0)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingRight() {
+        assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(125)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingBottom() {
+        assertThat(dp.getHotseatLayoutPadding(context).bottom).isEqualTo(448)
+    }
+
+    @Test
+    fun hotseatBarEndOffset() {
+        assertThat(dp.hotseatBarEndOffset).isEqualTo(0)
+    }
+
+    @Test
+    fun overviewGridRectLeft() {
+        assertThat(dp.overviewGridRect.left).isEqualTo(0)
+    }
+
+    @Test
+    fun overviewGridRectTop() {
+        assertThat(dp.overviewGridRect.top).isEqualTo(280)
+    }
+
+    @Test
+    fun overviewGridRectRight() {
+        assertThat(dp.overviewGridRect.right).isEqualTo(1440)
+    }
+
+    @Test
+    fun overviewGridRectBottom() {
+        assertThat(dp.overviewGridRect.bottom).isEqualTo(2768)
+    }
+
+    @Test
+    fun taskDimensionX() {
+        assertThat(dp.taskDimension.x).isEqualTo(1440)
+    }
+
+    @Test
+    fun taskDimensionY() {
+        assertThat(dp.taskDimension.y).isEqualTo(3120)
+    }
+
+    @Test
+    fun overviewTaskRectLeft() {
+        assertThat(dp.overviewTaskRect.left).isEqualTo(231)
+    }
+
+    @Test
+    fun overviewTaskRectTop() {
+        assertThat(dp.overviewTaskRect.top).isEqualTo(414)
+    }
+
+    @Test
+    fun overviewTaskRectRight() {
+        assertThat(dp.overviewTaskRect.right).isEqualTo(1209)
+    }
+
+    @Test
+    fun overviewTaskRectBottom() {
+        assertThat(dp.overviewTaskRect.bottom).isEqualTo(2533)
+    }
+
+    @Test
+    fun overviewGridTaskDimensionX() {
+        assertThat(dp.overviewGridTaskDimension.x).isEqualTo(450)
+    }
+
+    @Test
+    fun overviewGridTaskDimensionY() {
+        assertThat(dp.overviewGridTaskDimension.y).isEqualTo(976)
+    }
+
+    @Test
+    fun overviewModalTaskRectLeft() {
+        assertThat(dp.overviewModalTaskRect.left).isEqualTo(182)
+    }
+
+    @Test
+    fun overviewModalTaskRectTop() {
+        assertThat(dp.overviewModalTaskRect.top).isEqualTo(202)
+    }
+
+    @Test
+    fun overviewModalTaskRectRight() {
+        assertThat(dp.overviewModalTaskRect.right).isEqualTo(1258)
+    }
+
+    @Test
+    fun overviewModalTaskRectBottom() {
+        assertThat(dp.overviewModalTaskRect.bottom).isEqualTo(2533)
+    }
+
+    @Test
+    fun getGridTaskRectLeft() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).left).isEqualTo(759)
+    }
+
+    @Test
+    fun getGridTaskRectTop() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).top).isEqualTo(414)
+    }
+
+    @Test
+    fun getGridTaskRectRight() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).right).isEqualTo(1209)
+    }
+
+    @Test
+    fun getGridTaskRectBottom() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).bottom).isEqualTo(1390)
+    }
+
+    @Test
+    fun overviewTaskScale() {
+        assertThat(dp.overviewTaskWorkspaceScale).isEqualTo(0.86595833f)
+    }
+
+    @Test
+    fun overviewModalTaskScale() {
+        assertThat(dp.overviewModalTaskScale).isEqualTo(1.1000472f)
+    }
+}
\ No newline at end of file
diff --git a/quickstep/tests/src/com/android/quickstep/DeviceProfilePhoneTest.kt b/quickstep/tests/src/com/android/quickstep/DeviceProfilePhoneTest.kt
new file mode 100644
index 0000000..f91d03d
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/DeviceProfilePhoneTest.kt
@@ -0,0 +1,596 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.DeviceProfileBaseTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for DeviceProfile for phone.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceProfilePhoneTest : DeviceProfileBaseTest() {
+
+    lateinit var dp: DeviceProfile
+
+    @Before
+    fun before() {
+        initializeVarsForPhone()
+        dp = newDP()
+    }
+
+    @Test
+    fun isScalableGrid() {
+        assertThat(dp.isScalableGrid).isTrue()
+    }
+
+    @Test
+    fun cellWidthPx() {
+        assertThat(dp.cellWidthPx).isEqualTo(265)
+    }
+
+    @Test
+    fun cellHeightPx() {
+        assertThat(dp.cellHeightPx).isEqualTo(343)
+    }
+
+    @Test
+    fun getCellSizeX() {
+        assertThat(dp.getCellSize().x).isEqualTo(265)
+    }
+
+    @Test
+    fun getCellSizeY() {
+        assertThat(dp.getCellSize().y).isEqualTo(552)
+    }
+
+    @Test
+    fun cellLayoutBorderSpacePxX() {
+        assertThat(dp.cellLayoutBorderSpacePx.x).isEqualTo(66)
+    }
+
+    @Test
+    fun cellLayoutBorderSpacePxY() {
+        assertThat(dp.cellLayoutBorderSpacePx.y).isEqualTo(66)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxLeft() {
+        assertThat(dp.cellLayoutPaddingPx.left).isEqualTo(38)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxTop() {
+        assertThat(dp.cellLayoutPaddingPx.top).isEqualTo(0)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxRight() {
+        assertThat(dp.cellLayoutPaddingPx.right).isEqualTo(38)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxBottom() {
+        assertThat(dp.cellLayoutPaddingPx.bottom).isEqualTo(38)
+    }
+
+    @Test
+    fun iconSizePx() {
+        assertThat(dp.iconSizePx).isEqualTo(196)
+    }
+
+    @Test
+    fun iconTextSizePx() {
+        assertThat(dp.iconTextSizePx).isEqualTo(49)
+    }
+
+    @Test
+    fun iconDrawablePaddingPx() {
+        assertThat(dp.iconDrawablePaddingPx).isEqualTo(25)
+    }
+
+    @Test
+    fun folderCellWidthPx() {
+        assertThat(dp.folderCellWidthPx).isEqualTo(265)
+    }
+
+    @Test
+    fun folderCellHeightPx() {
+        assertThat(dp.folderCellHeightPx).isEqualTo(343)
+    }
+
+    @Test
+    fun folderChildIconSizePx() {
+        assertThat(dp.folderChildIconSizePx).isEqualTo(196)
+    }
+
+    @Test
+    fun folderChildTextSizePx() {
+        assertThat(dp.folderChildTextSizePx).isEqualTo(49)
+    }
+
+    @Test
+    fun folderChildDrawablePaddingPx() {
+        assertThat(dp.folderChildDrawablePaddingPx).isEqualTo(27)
+    }
+
+    @Test
+    fun folderCellLayoutBorderSpaceOriginalPx() {
+        assertThat(dp.folderCellLayoutBorderSpaceOriginalPx).isEqualTo(0)
+    }
+
+    @Test
+    fun folderCellLayoutBorderSpacePxX() {
+        assertThat(dp.folderCellLayoutBorderSpacePx.x).isEqualTo(0)
+    }
+
+    @Test
+    fun folderCellLayoutBorderSpacePxY() {
+        assertThat(dp.folderCellLayoutBorderSpacePx.y).isEqualTo(0)
+    }
+
+    @Test
+    fun bottomSheetTopPadding() {
+        assertThat(dp.bottomSheetTopPadding).isEqualTo(38)
+    }
+
+    @Test
+    fun allAppsShiftRange() {
+        assertThat(dp.allAppsShiftRange).isEqualTo(1050)
+    }
+
+    @Test
+    fun allAppsTopPadding() {
+        assertThat(dp.allAppsTopPadding).isEqualTo(0)
+    }
+
+    @Test
+    fun allAppsIconSizePx() {
+        assertThat(dp.allAppsIconSizePx).isEqualTo(232)
+    }
+
+    @Test
+    fun allAppsIconTextSizePx() {
+        assertThat(dp.allAppsIconTextSizePx).isEqualTo(58)
+    }
+
+    @Test
+    fun allAppsIconDrawablePaddingPx() {
+        assertThat(dp.allAppsIconDrawablePaddingPx).isEqualTo(25)
+    }
+
+    @Test
+    fun allAppsCellHeightPx() {
+        assertThat(dp.allAppsCellHeightPx).isEqualTo(409)
+    }
+
+    @Test
+    fun allAppsCellWidthPx() {
+        assertThat(dp.allAppsCellWidthPx).isEqualTo(265)
+    }
+
+    @Test
+    fun allAppsBorderSpacePxX() {
+        assertThat(dp.allAppsBorderSpacePx.x).isEqualTo(66)
+    }
+
+    @Test
+    fun allAppsBorderSpacePxY() {
+        assertThat(dp.allAppsBorderSpacePx.y).isEqualTo(66)
+    }
+
+    @Test
+    fun numShownAllAppsColumns() {
+        assertThat(dp.numShownAllAppsColumns).isEqualTo(0)
+    }
+
+    @Test
+    fun allAppsLeftRightPadding() {
+        assertThat(dp.allAppsLeftRightPadding).isEqualTo(91)
+    }
+
+    @Test
+    fun allAppsLeftRightMargin() {
+        assertThat(dp.allAppsLeftRightMargin).isEqualTo(0)
+    }
+
+    @Test
+    fun hotseatBarSizePx() {
+        assertThat(dp.hotseatBarSizePx).isEqualTo(669)
+    }
+
+    @Test
+    fun hotseatCellHeightPx() {
+        assertThat(dp.hotseatCellHeightPx).isEqualTo(221)
+    }
+
+    @Test
+    fun hotseatBarBottomPaddingPx() {
+        assertThat(dp.hotseatBarBottomSpacePx).isEqualTo(168)
+    }
+
+    @Test
+    fun hotseatBarSidePaddingStartPx() {
+        assertThat(dp.hotseatBarSidePaddingStartPx).isEqualTo(0)
+    }
+
+    @Test
+    fun hotseatBarSidePaddingEndPx() {
+        assertThat(dp.hotseatBarSidePaddingEndPx).isEqualTo(0)
+    }
+
+    @Test
+    fun hotseatQsbSpace() {
+        assertThat(dp.hotseatQsbSpace).isEqualTo(126)
+    }
+
+    @Test
+    fun hotseatQsbHeight() {
+        assertThat(dp.hotseatQsbHeight).isEqualTo(221)
+    }
+
+    @Test
+    fun springLoadedHotseatBarTopMarginPx() {
+        assertThat(dp.springLoadedHotseatBarTopMarginPx).isEqualTo(266)
+    }
+
+    @Test
+    fun numShownHotseatIcons() {
+        assertThat(dp.numShownHotseatIcons).isEqualTo(4)
+    }
+
+    @Test
+    fun hotseatBorderSpace() {
+        assertThat(dp.hotseatBorderSpace).isEqualTo(135)
+    }
+
+    @Test
+    fun isQsbInline() {
+        assertThat(dp.isQsbInline).isEqualTo(false)
+    }
+
+    @Test
+    fun qsbWidth() {
+        assertThat(dp.qsbWidth).isEqualTo(1189)
+    }
+
+    @Test
+    fun isTaskbarPresent() {
+        assertThat(dp.isTaskbarPresent).isEqualTo(false)
+    }
+
+    @Test
+    fun isTaskbarPresentInApps() {
+        assertThat(dp.isTaskbarPresentInApps).isEqualTo(false)
+    }
+
+    @Test
+    fun taskbarSize() {
+        assertThat(dp.taskbarSize).isEqualTo(0)
+    }
+
+    @Test
+    fun desiredWorkspaceHorizontalMarginPx() {
+        assertThat(dp.desiredWorkspaceHorizontalMarginPx).isEqualTo(91)
+    }
+
+    @Test
+    fun workspacePaddingLeft() {
+        assertThat(dp.workspacePadding.left).isEqualTo(53)
+    }
+
+    @Test
+    fun workspacePaddingTop() {
+        assertThat(dp.workspacePadding.top).isEqualTo(0)
+    }
+
+    @Test
+    fun workspacePaddingRight() {
+        assertThat(dp.workspacePadding.right).isEqualTo(53)
+    }
+
+    @Test
+    fun workspacePaddingBottom() {
+        assertThat(dp.workspacePadding.bottom).isEqualTo(573)
+    }
+
+    @Test
+    fun iconScale() {
+        assertThat(dp.iconScale).isEqualTo(1)
+    }
+
+    @Test
+    fun cellScaleToFit() {
+        assertThat(dp.cellScaleToFit).isEqualTo(1.182266f)
+    }
+
+    @Test
+    fun workspaceTopPadding() {
+        assertThat(dp.workspaceTopPadding).isEqualTo(0)
+    }
+
+    @Test
+    fun workspaceBottomPadding() {
+        assertThat(dp.workspaceBottomPadding).isEqualTo(0)
+    }
+
+    @Test
+    fun overviewTaskMarginPx() {
+        assertThat(dp.overviewTaskMarginPx).isEqualTo(56)
+    }
+
+    @Test
+    fun overviewTaskMarginGridPx() {
+        assertThat(dp.overviewTaskMarginGridPx).isEqualTo(0)
+    }
+
+    @Test
+    fun overviewTaskIconSizePx() {
+        assertThat(dp.overviewTaskIconSizePx).isEqualTo(168)
+    }
+
+    @Test
+    fun overviewTaskIconDrawableSizePx() {
+        assertThat(dp.overviewTaskIconDrawableSizePx).isEqualTo(154)
+    }
+
+    @Test
+    fun overviewTaskIconDrawableSizeGridPx() {
+        assertThat(dp.overviewTaskIconDrawableSizeGridPx).isEqualTo(0)
+    }
+
+    @Test
+    fun overviewTaskThumbnailTopMarginPx() {
+        assertThat(dp.overviewTaskThumbnailTopMarginPx).isEqualTo(280)
+    }
+
+    @Test
+    fun overviewActionsTopMarginPx() {
+        assertThat(dp.overviewActionsTopMarginPx).isEqualTo(84)
+    }
+
+    @Test
+    fun overviewActionsHeight() {
+        assertThat(dp.overviewActionsHeight).isEqualTo(168)
+    }
+
+    @Test
+    fun overviewActionsButtonSpacing() {
+        assertThat(dp.overviewActionsButtonSpacing).isEqualTo(126)
+    }
+
+    @Test
+    fun overviewPageSpacing() {
+        assertThat(dp.overviewPageSpacing).isEqualTo(56)
+    }
+
+    @Test
+    fun overviewRowSpacing() {
+        assertThat(dp.overviewRowSpacing).isEqualTo(-112)
+    }
+
+    @Test
+    fun overviewGridSideMargin() {
+        assertThat(dp.overviewGridSideMargin).isEqualTo(0)
+    }
+
+    @Test
+    fun dropTargetBarTopMarginPx() {
+        assertThat(dp.dropTargetBarTopMarginPx).isEqualTo(112)
+    }
+
+    @Test
+    fun dropTargetBarSizePx() {
+        assertThat(dp.dropTargetBarSizePx).isEqualTo(196)
+    }
+
+    @Test
+    fun dropTargetBarBottomMarginPx() {
+        assertThat(dp.dropTargetBarBottomMarginPx).isEqualTo(56)
+    }
+
+    @Test
+    fun workspaceSpringLoadedMinNextPageVisiblePx() {
+        assertThat(dp.workspaceSpringLoadedMinNextPageVisiblePx).isEqualTo(84)
+    }
+
+    @Test
+    fun getWorkspaceSpringLoadScale() {
+        assertThat(dp.workspaceSpringLoadScale).isEqualTo(0.74417657f)
+    }
+
+    @Test
+    fun getCellLayoutHeight() {
+        assertThat(dp.cellLayoutHeight).isEqualTo(2447)
+    }
+
+    @Test
+    fun getCellLayoutWidth() {
+        assertThat(dp.cellLayoutWidth).isEqualTo(1334)
+    }
+
+    @Test
+    fun getPanelCount() {
+        assertThat(dp.panelCount).isEqualTo(1)
+    }
+
+    @Test
+    fun isVerticalBarLayout() {
+        assertThat(dp.isVerticalBarLayout).isEqualTo(false)
+    }
+
+    @Test
+    fun getCellLayoutSpringLoadShrunkTop() {
+        assertThat(dp.cellLayoutSpringLoadShrunkTop).isEqualTo(364)
+    }
+
+    @Test
+    fun getCellLayoutSpringLoadShrunkBottom() {
+        assertThat(dp.cellLayoutSpringLoadShrunkBottom).isEqualTo(2185)
+    }
+
+    @Test
+    fun getQsbOffsetY() {
+        assertThat(dp.qsbOffsetY).isEqualTo(147)
+    }
+
+    @Test
+    fun getTaskbarOffsetY() {
+        assertThat(dp.taskbarOffsetY).isEqualTo(558)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingLeft() {
+        assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(125)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingTop() {
+        assertThat(dp.getHotseatLayoutPadding(context).top).isEqualTo(0)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingRight() {
+        assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(125)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingBottom() {
+        assertThat(dp.getHotseatLayoutPadding(context).bottom).isEqualTo(448)
+    }
+
+    @Test
+    fun hotseatBarEndOffset() {
+        assertThat(dp.hotseatBarEndOffset).isEqualTo(0)
+    }
+
+    @Test
+    fun overviewGridRectLeft() {
+        assertThat(dp.overviewGridRect.left).isEqualTo(0)
+    }
+
+    @Test
+    fun overviewGridRectTop() {
+        assertThat(dp.overviewGridRect.top).isEqualTo(280)
+    }
+
+    @Test
+    fun overviewGridRectRight() {
+        assertThat(dp.overviewGridRect.right).isEqualTo(1440)
+    }
+
+    @Test
+    fun overviewGridRectBottom() {
+        assertThat(dp.overviewGridRect.bottom).isEqualTo(2768)
+    }
+
+    @Test
+    fun taskDimensionX() {
+        assertThat(dp.taskDimension.x).isEqualTo(1440)
+    }
+
+    @Test
+    fun taskDimensionY() {
+        assertThat(dp.taskDimension.y).isEqualTo(3120)
+    }
+
+    @Test
+    fun overviewTaskRectLeft() {
+        assertThat(dp.overviewTaskRect.left).isEqualTo(231)
+    }
+
+    @Test
+    fun overviewTaskRectTop() {
+        assertThat(dp.overviewTaskRect.top).isEqualTo(414)
+    }
+
+    @Test
+    fun overviewTaskRectRight() {
+        assertThat(dp.overviewTaskRect.right).isEqualTo(1209)
+    }
+
+    @Test
+    fun overviewTaskRectBottom() {
+        assertThat(dp.overviewTaskRect.bottom).isEqualTo(2533)
+    }
+
+    @Test
+    fun overviewGridTaskDimensionX() {
+        assertThat(dp.overviewGridTaskDimension.x).isEqualTo(450)
+    }
+
+    @Test
+    fun overviewGridTaskDimensionY() {
+        assertThat(dp.overviewGridTaskDimension.y).isEqualTo(976)
+    }
+
+    @Test
+    fun overviewModalTaskRectLeft() {
+        assertThat(dp.overviewModalTaskRect.left).isEqualTo(182)
+    }
+
+    @Test
+    fun overviewModalTaskRectTop() {
+        assertThat(dp.overviewModalTaskRect.top).isEqualTo(202)
+    }
+
+    @Test
+    fun overviewModalTaskRectRight() {
+        assertThat(dp.overviewModalTaskRect.right).isEqualTo(1258)
+    }
+
+    @Test
+    fun overviewModalTaskRectBottom() {
+        assertThat(dp.overviewModalTaskRect.bottom).isEqualTo(2533)
+    }
+
+    @Test
+    fun getGridTaskRectLeft() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).left).isEqualTo(759)
+    }
+
+    @Test
+    fun getGridTaskRectTop() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).top).isEqualTo(414)
+    }
+
+    @Test
+    fun getGridTaskRectRight() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).right).isEqualTo(1209)
+    }
+
+    @Test
+    fun getGridTaskRectBottom() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).bottom).isEqualTo(1390)
+    }
+
+    @Test
+    fun overviewTaskScale() {
+        assertThat(dp.overviewTaskWorkspaceScale).isEqualTo(0.86595833f)
+    }
+
+    @Test
+    fun overviewModalTaskScale() {
+        assertThat(dp.overviewModalTaskScale).isEqualTo(1.1000472f)
+    }
+}
\ No newline at end of file
diff --git a/quickstep/tests/src/com/android/quickstep/DeviceProfileTabletLandscape3ButtonTest.kt b/quickstep/tests/src/com/android/quickstep/DeviceProfileTabletLandscape3ButtonTest.kt
new file mode 100644
index 0000000..2d9e263
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/DeviceProfileTabletLandscape3ButtonTest.kt
@@ -0,0 +1,596 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.DeviceProfileBaseTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for DeviceProfile for tablet in landscape with 3-Button navigation.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceProfileTabletLandscape3ButtonTest : DeviceProfileBaseTest() {
+
+    lateinit var dp: DeviceProfile
+
+    @Before
+    fun before() {
+        initializeVarsForTablet(isLandscape = true, isGestureMode = false)
+        dp = newDP()
+    }
+
+    @Test
+    fun isScalableGrid() {
+        assertThat(dp.isScalableGrid).isTrue()
+    }
+
+    @Test
+    fun cellWidthPx() {
+        assertThat(dp.cellWidthPx).isEqualTo(227)
+    }
+
+    @Test
+    fun cellHeightPx() {
+        assertThat(dp.cellHeightPx).isEqualTo(294)
+    }
+
+    @Test
+    fun getCellSizeX() {
+        assertThat(dp.cellSize.x).isEqualTo(558)
+    }
+
+    @Test
+    fun getCellSizeY() {
+        assertThat(dp.cellSize.y).isEqualTo(294)
+    }
+
+    @Test
+    fun cellLayoutBorderSpacePxX() {
+        assertThat(dp.cellLayoutBorderSpacePx.x).isEqualTo(57)
+    }
+
+    @Test
+    fun cellLayoutBorderSpacePxY() {
+        assertThat(dp.cellLayoutBorderSpacePx.y).isEqualTo(57)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxLeft() {
+        assertThat(dp.cellLayoutPaddingPx.left).isEqualTo(59)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxTop() {
+        assertThat(dp.cellLayoutPaddingPx.top).isEqualTo(0)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxRight() {
+        assertThat(dp.cellLayoutPaddingPx.right).isEqualTo(59)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxBottom() {
+        assertThat(dp.cellLayoutPaddingPx.bottom).isEqualTo(59)
+    }
+
+    @Test
+    fun iconSizePx() {
+        assertThat(dp.iconSizePx).isEqualTo(112)
+    }
+
+    @Test
+    fun iconTextSizePx() {
+        assertThat(dp.iconTextSizePx).isEqualTo(28)
+    }
+
+    @Test
+    fun iconDrawablePaddingPx() {
+        assertThat(dp.iconDrawablePaddingPx).isEqualTo(14)
+    }
+
+    @Test
+    fun folderCellWidthPx() {
+        assertThat(dp.folderCellWidthPx).isEqualTo(227)
+    }
+
+    @Test
+    fun folderCellHeightPx() {
+        assertThat(dp.folderCellHeightPx).isEqualTo(294)
+    }
+
+    @Test
+    fun folderChildIconSizePx() {
+        assertThat(dp.folderChildIconSizePx).isEqualTo(112)
+    }
+
+    @Test
+    fun folderChildTextSizePx() {
+        assertThat(dp.folderChildTextSizePx).isEqualTo(28)
+    }
+
+    @Test
+    fun folderChildDrawablePaddingPx() {
+        assertThat(dp.folderChildDrawablePaddingPx).isEqualTo(48)
+    }
+
+    @Test
+    fun folderCellLayoutBorderSpaceOriginalPx() {
+        assertThat(dp.folderCellLayoutBorderSpaceOriginalPx).isEqualTo(0)
+    }
+
+    @Test
+    fun folderCellLayoutBorderSpacePxX() {
+        assertThat(dp.folderCellLayoutBorderSpacePx.x).isEqualTo(0)
+    }
+
+    @Test
+    fun folderCellLayoutBorderSpacePxY() {
+        assertThat(dp.folderCellLayoutBorderSpacePx.y).isEqualTo(0)
+    }
+
+    @Test
+    fun bottomSheetTopPadding() {
+        assertThat(dp.bottomSheetTopPadding).isEqualTo(0)
+    }
+
+    @Test
+    fun allAppsShiftRange() {
+        assertThat(dp.allAppsShiftRange).isEqualTo(1600)
+    }
+
+    @Test
+    fun allAppsTopPadding() {
+        assertThat(dp.allAppsTopPadding).isEqualTo(0)
+    }
+
+    @Test
+    fun allAppsIconSizePx() {
+        assertThat(dp.allAppsIconSizePx).isEqualTo(199)
+    }
+
+    @Test
+    fun allAppsIconTextSizePx() {
+        assertThat(dp.allAppsIconTextSizePx).isEqualTo(50)
+    }
+
+    @Test
+    fun allAppsIconDrawablePaddingPx() {
+        assertThat(dp.allAppsIconDrawablePaddingPx).isEqualTo(14)
+    }
+
+    @Test
+    fun allAppsCellHeightPx() {
+        assertThat(dp.allAppsCellHeightPx).isEqualTo(351)
+    }
+
+    @Test
+    fun allAppsCellWidthPx() {
+        assertThat(dp.allAppsCellWidthPx).isEqualTo(227)
+    }
+
+    @Test
+    fun allAppsBorderSpacePxX() {
+        assertThat(dp.allAppsBorderSpacePx.x).isEqualTo(57)
+    }
+
+    @Test
+    fun allAppsBorderSpacePxY() {
+        assertThat(dp.allAppsBorderSpacePx.y).isEqualTo(57)
+    }
+
+    @Test
+    fun numShownAllAppsColumns() {
+        assertThat(dp.numShownAllAppsColumns).isEqualTo(0)
+    }
+
+    @Test
+    fun allAppsLeftRightPadding() {
+        assertThat(dp.allAppsLeftRightPadding).isEqualTo(64)
+    }
+
+    @Test
+    fun allAppsLeftRightMargin() {
+        assertThat(dp.allAppsLeftRightMargin).isEqualTo(1244)
+    }
+
+    @Test
+    fun hotseatBarSizePx() {
+        assertThat(dp.hotseatBarSizePx).isEqualTo(228)
+    }
+
+    @Test
+    fun hotseatCellHeightPx() {
+        assertThat(dp.hotseatCellHeightPx).isEqualTo(126)
+    }
+
+    @Test
+    fun hotseatBarBottomSpacePx() {
+        assertThat(dp.hotseatBarBottomSpacePx).isEqualTo(116)
+    }
+
+    @Test
+    fun hotseatBarSidePaddingStartPx() {
+        assertThat(dp.hotseatBarSidePaddingStartPx).isEqualTo(0)
+    }
+
+    @Test
+    fun hotseatBarSidePaddingEndPx() {
+        assertThat(dp.hotseatBarSidePaddingEndPx).isEqualTo(0)
+    }
+
+    @Test
+    fun hotseatQsbSpace() {
+        assertThat(dp.hotseatQsbSpace).isEqualTo(56)
+    }
+
+    @Test
+    fun hotseatQsbHeight() {
+        assertThat(dp.hotseatQsbHeight).isEqualTo(126)
+    }
+
+    @Test
+    fun springLoadedHotseatBarTopMarginPx() {
+        assertThat(dp.springLoadedHotseatBarTopMarginPx).isEqualTo(128)
+    }
+
+    @Test
+    fun numShownHotseatIcons() {
+        assertThat(dp.numShownHotseatIcons).isEqualTo(5)
+    }
+
+    @Test
+    fun hotseatBorderSpace() {
+        assertThat(dp.hotseatBorderSpace).isEqualTo(32)
+    }
+
+    @Test
+    fun isQsbInline() {
+        assertThat(dp.isQsbInline).isEqualTo(true)
+    }
+
+    @Test
+    fun qsbWidth() {
+        assertThat(dp.qsbWidth).isEqualTo(1237)
+    }
+
+    @Test
+    fun isTaskbarPresent() {
+        assertThat(dp.isTaskbarPresent).isEqualTo(true)
+    }
+
+    @Test
+    fun isTaskbarPresentInApps() {
+        assertThat(dp.isTaskbarPresentInApps).isEqualTo(false)
+    }
+
+    @Test
+    fun taskbarSize() {
+        assertThat(dp.taskbarSize).isEqualTo(120)
+    }
+
+    @Test
+    fun desiredWorkspaceHorizontalMarginPx() {
+        assertThat(dp.desiredWorkspaceHorizontalMarginPx).isEqualTo(78)
+    }
+
+    @Test
+    fun workspacePaddingLeft() {
+        assertThat(dp.workspacePadding.left).isEqualTo(19)
+    }
+
+    @Test
+    fun workspacePaddingTop() {
+        assertThat(dp.workspacePadding.top).isEqualTo(0)
+    }
+
+    @Test
+    fun workspacePaddingRight() {
+        assertThat(dp.workspacePadding.right).isEqualTo(19)
+    }
+
+    @Test
+    fun workspacePaddingBottom() {
+        assertThat(dp.workspacePadding.bottom).isEqualTo(93)
+    }
+
+    @Test
+    fun iconScale() {
+        assertThat(dp.iconScale).isEqualTo(1)
+    }
+
+    @Test
+    fun cellScaleToFit() {
+        assertThat(dp.cellScaleToFit).isEqualTo(1.7736843f)
+    }
+
+    @Test
+    fun workspaceTopPadding() {
+        assertThat(dp.workspaceTopPadding).isEqualTo(0)
+    }
+
+    @Test
+    fun workspaceBottomPadding() {
+        assertThat(dp.workspaceBottomPadding).isEqualTo(0)
+    }
+
+    @Test
+    fun overviewTaskMarginPx() {
+        assertThat(dp.overviewTaskMarginPx).isEqualTo(32)
+    }
+
+    @Test
+    fun overviewTaskMarginGridPx() {
+        assertThat(dp.overviewTaskMarginGridPx).isEqualTo(32)
+    }
+
+    @Test
+    fun overviewTaskIconSizePx() {
+        assertThat(dp.overviewTaskIconSizePx).isEqualTo(96)
+    }
+
+    @Test
+    fun overviewTaskIconDrawableSizePx() {
+        assertThat(dp.overviewTaskIconDrawableSizePx).isEqualTo(88)
+    }
+
+    @Test
+    fun overviewTaskIconDrawableSizeGridPx() {
+        assertThat(dp.overviewTaskIconDrawableSizeGridPx).isEqualTo(88)
+    }
+
+    @Test
+    fun overviewTaskThumbnailTopMarginPx() {
+        assertThat(dp.overviewTaskThumbnailTopMarginPx).isEqualTo(160)
+    }
+
+    @Test
+    fun overviewActionsTopMarginPx() {
+        assertThat(dp.overviewActionsTopMarginPx).isEqualTo(40)
+    }
+
+    @Test
+    fun overviewActionsHeight() {
+        assertThat(dp.overviewActionsHeight).isEqualTo(96)
+    }
+
+    @Test
+    fun overviewActionsButtonSpacing() {
+        assertThat(dp.overviewActionsButtonSpacing).isEqualTo(72)
+    }
+
+    @Test
+    fun overviewPageSpacing() {
+        assertThat(dp.overviewPageSpacing).isEqualTo(88)
+    }
+
+    @Test
+    fun overviewRowSpacing() {
+        assertThat(dp.overviewRowSpacing).isEqualTo(40)
+    }
+
+    @Test
+    fun overviewGridSideMargin() {
+        assertThat(dp.overviewGridSideMargin).isEqualTo(128)
+    }
+
+    @Test
+    fun dropTargetBarTopMarginPx() {
+        assertThat(dp.dropTargetBarTopMarginPx).isEqualTo(0)
+    }
+
+    @Test
+    fun dropTargetBarSizePx() {
+        assertThat(dp.dropTargetBarSizePx).isEqualTo(144)
+    }
+
+    @Test
+    fun dropTargetBarBottomMarginPx() {
+        assertThat(dp.dropTargetBarBottomMarginPx).isEqualTo(64)
+    }
+
+    @Test
+    fun workspaceSpringLoadedMinNextPageVisiblePx() {
+        assertThat(dp.workspaceSpringLoadedMinNextPageVisiblePx).isEqualTo(48)
+    }
+
+    @Test
+    fun getWorkspaceSpringLoadScale() {
+        assertThat(dp.workspaceSpringLoadScale).isEqualTo(0.7363184f)
+    }
+
+    @Test
+    fun getCellLayoutHeight() {
+        assertThat(dp.cellLayoutHeight).isEqualTo(1407)
+    }
+
+    @Test
+    fun getCellLayoutWidth() {
+        assertThat(dp.cellLayoutWidth).isEqualTo(2522)
+    }
+
+    @Test
+    fun getPanelCount() {
+        assertThat(dp.panelCount).isEqualTo(1)
+    }
+
+    @Test
+    fun isVerticalBarLayout() {
+        assertThat(dp.isVerticalBarLayout).isEqualTo(false)
+    }
+
+    @Test
+    fun getCellLayoutSpringLoadShrunkTop() {
+        assertThat(dp.cellLayoutSpringLoadShrunkTop).isEqualTo(208)
+    }
+
+    @Test
+    fun getCellLayoutSpringLoadShrunkBottom() {
+        assertThat(dp.cellLayoutSpringLoadShrunkBottom).isEqualTo(1244)
+    }
+
+    @Test
+    fun getQsbOffsetY() {
+        assertThat(dp.qsbOffsetY).isEqualTo(109)
+    }
+
+    @Test
+    fun getTaskbarOffsetY() {
+        assertThat(dp.taskbarOffsetY).isEqualTo(112)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingLeft() {
+        assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(1443)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingTop() {
+        assertThat(dp.getHotseatLayoutPadding(context).top).isEqualTo(-7)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingRight() {
+        assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(428)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingBottom() {
+        assertThat(dp.getHotseatLayoutPadding(context).bottom).isEqualTo(109)
+    }
+
+    @Test
+    fun hotseatBarEndOffset() {
+        assertThat(dp.hotseatBarEndOffset).isEqualTo(428)
+    }
+
+    @Test
+    fun overviewGridRectLeft() {
+        assertThat(dp.overviewGridRect.left).isEqualTo(128)
+    }
+
+    @Test
+    fun overviewGridRectTop() {
+        assertThat(dp.overviewGridRect.top).isEqualTo(160)
+    }
+
+    @Test
+    fun overviewGridRectRight() {
+        assertThat(dp.overviewGridRect.right).isEqualTo(2432)
+    }
+
+    @Test
+    fun overviewGridRectBottom() {
+        assertThat(dp.overviewGridRect.bottom).isEqualTo(1340)
+    }
+
+    @Test
+    fun taskDimensionX() {
+        assertThat(dp.taskDimension.x).isEqualTo(2560)
+    }
+
+    @Test
+    fun taskDimensionY() {
+        assertThat(dp.taskDimension.y).isEqualTo(1480)
+    }
+
+    @Test
+    fun overviewTaskRectLeft() {
+        assertThat(dp.overviewTaskRect.left).isEqualTo(384)
+    }
+
+    @Test
+    fun overviewTaskRectTop() {
+        assertThat(dp.overviewTaskRect.top).isEqualTo(232)
+    }
+
+    @Test
+    fun overviewTaskRectRight() {
+        assertThat(dp.overviewTaskRect.right).isEqualTo(2176)
+    }
+
+    @Test
+    fun overviewTaskRectBottom() {
+        assertThat(dp.overviewTaskRect.bottom).isEqualTo(1268)
+    }
+
+    @Test
+    fun overviewGridTaskDimensionX() {
+        assertThat(dp.overviewGridTaskDimension.x).isEqualTo(723)
+    }
+
+    @Test
+    fun overviewGridTaskDimensionY() {
+        assertThat(dp.overviewGridTaskDimension.y).isEqualTo(418)
+    }
+
+    @Test
+    fun overviewModalTaskRectLeft() {
+        assertThat(dp.overviewModalTaskRect.left).isEqualTo(294)
+    }
+
+    @Test
+    fun overviewModalTaskRectTop() {
+        assertThat(dp.overviewModalTaskRect.top).isEqualTo(128)
+    }
+
+    @Test
+    fun overviewModalTaskRectRight() {
+        assertThat(dp.overviewModalTaskRect.right).isEqualTo(2266)
+    }
+
+    @Test
+    fun overviewModalTaskRectBottom() {
+        assertThat(dp.overviewModalTaskRect.bottom).isEqualTo(1268)
+    }
+
+    @Test
+    fun getGridTaskRectLeft() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).left).isEqualTo(1453)
+    }
+
+    @Test
+    fun getGridTaskRectTop() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).top).isEqualTo(232)
+    }
+
+    @Test
+    fun getGridTaskRectRight() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).right).isEqualTo(2176)
+    }
+
+    @Test
+    fun getGridTaskRectBottom() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).bottom).isEqualTo(650)
+    }
+
+    @Test
+    fun overviewTaskScale() {
+        assertThat(dp.overviewTaskWorkspaceScale).isEqualTo(0.7363184f)
+    }
+
+    @Test
+    fun overviewModalTaskScale() {
+        assertThat(dp.overviewModalTaskScale).isEqualTo(1.1003861f)
+    }
+}
\ No newline at end of file
diff --git a/quickstep/tests/src/com/android/quickstep/DeviceProfileTabletLandscapeTest.kt b/quickstep/tests/src/com/android/quickstep/DeviceProfileTabletLandscapeTest.kt
new file mode 100644
index 0000000..3add2c7
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/DeviceProfileTabletLandscapeTest.kt
@@ -0,0 +1,596 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.DeviceProfileBaseTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for DeviceProfile for tablet in landscape.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceProfileTabletLandscapeTest : DeviceProfileBaseTest() {
+
+    lateinit var dp: DeviceProfile
+
+    @Before
+    fun before() {
+        initializeVarsForTablet(isLandscape = true)
+        dp = newDP()
+    }
+
+    @Test
+    fun isScalableGrid() {
+        assertThat(dp.isScalableGrid).isTrue()
+    }
+
+    @Test
+    fun cellWidthPx() {
+        assertThat(dp.cellWidthPx).isEqualTo(227)
+    }
+
+    @Test
+    fun cellHeightPx() {
+        assertThat(dp.cellHeightPx).isEqualTo(294)
+    }
+
+    @Test
+    fun getCellSizeX() {
+        assertThat(dp.cellSize.x).isEqualTo(558)
+    }
+
+    @Test
+    fun getCellSizeY() {
+        assertThat(dp.cellSize.y).isEqualTo(294)
+    }
+
+    @Test
+    fun cellLayoutBorderSpacePxX() {
+        assertThat(dp.cellLayoutBorderSpacePx.x).isEqualTo(57)
+    }
+
+    @Test
+    fun cellLayoutBorderSpacePxY() {
+        assertThat(dp.cellLayoutBorderSpacePx.y).isEqualTo(57)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxLeft() {
+        assertThat(dp.cellLayoutPaddingPx.left).isEqualTo(59)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxTop() {
+        assertThat(dp.cellLayoutPaddingPx.top).isEqualTo(0)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxRight() {
+        assertThat(dp.cellLayoutPaddingPx.right).isEqualTo(59)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxBottom() {
+        assertThat(dp.cellLayoutPaddingPx.bottom).isEqualTo(59)
+    }
+
+    @Test
+    fun iconSizePx() {
+        assertThat(dp.iconSizePx).isEqualTo(112)
+    }
+
+    @Test
+    fun iconTextSizePx() {
+        assertThat(dp.iconTextSizePx).isEqualTo(28)
+    }
+
+    @Test
+    fun iconDrawablePaddingPx() {
+        assertThat(dp.iconDrawablePaddingPx).isEqualTo(14)
+    }
+
+    @Test
+    fun folderCellWidthPx() {
+        assertThat(dp.folderCellWidthPx).isEqualTo(227)
+    }
+
+    @Test
+    fun folderCellHeightPx() {
+        assertThat(dp.folderCellHeightPx).isEqualTo(294)
+    }
+
+    @Test
+    fun folderChildIconSizePx() {
+        assertThat(dp.folderChildIconSizePx).isEqualTo(112)
+    }
+
+    @Test
+    fun folderChildTextSizePx() {
+        assertThat(dp.folderChildTextSizePx).isEqualTo(28)
+    }
+
+    @Test
+    fun folderChildDrawablePaddingPx() {
+        assertThat(dp.folderChildDrawablePaddingPx).isEqualTo(48)
+    }
+
+    @Test
+    fun folderCellLayoutBorderSpaceOriginalPx() {
+        assertThat(dp.folderCellLayoutBorderSpaceOriginalPx).isEqualTo(0)
+    }
+
+    @Test
+    fun folderCellLayoutBorderSpacePxX() {
+        assertThat(dp.folderCellLayoutBorderSpacePx.x).isEqualTo(0)
+    }
+
+    @Test
+    fun folderCellLayoutBorderSpacePxY() {
+        assertThat(dp.folderCellLayoutBorderSpacePx.y).isEqualTo(0)
+    }
+
+    @Test
+    fun bottomSheetTopPadding() {
+        assertThat(dp.bottomSheetTopPadding).isEqualTo(0)
+    }
+
+    @Test
+    fun allAppsShiftRange() {
+        assertThat(dp.allAppsShiftRange).isEqualTo(1600)
+    }
+
+    @Test
+    fun allAppsTopPadding() {
+        assertThat(dp.allAppsTopPadding).isEqualTo(0)
+    }
+
+    @Test
+    fun allAppsIconSizePx() {
+        assertThat(dp.allAppsIconSizePx).isEqualTo(199)
+    }
+
+    @Test
+    fun allAppsIconTextSizePx() {
+        assertThat(dp.allAppsIconTextSizePx).isEqualTo(50)
+    }
+
+    @Test
+    fun allAppsIconDrawablePaddingPx() {
+        assertThat(dp.allAppsIconDrawablePaddingPx).isEqualTo(14)
+    }
+
+    @Test
+    fun allAppsCellHeightPx() {
+        assertThat(dp.allAppsCellHeightPx).isEqualTo(351)
+    }
+
+    @Test
+    fun allAppsCellWidthPx() {
+        assertThat(dp.allAppsCellWidthPx).isEqualTo(227)
+    }
+
+    @Test
+    fun allAppsBorderSpacePxX() {
+        assertThat(dp.allAppsBorderSpacePx.x).isEqualTo(57)
+    }
+
+    @Test
+    fun allAppsBorderSpacePxY() {
+        assertThat(dp.allAppsBorderSpacePx.y).isEqualTo(57)
+    }
+
+    @Test
+    fun numShownAllAppsColumns() {
+        assertThat(dp.numShownAllAppsColumns).isEqualTo(0)
+    }
+
+    @Test
+    fun allAppsLeftRightPadding() {
+        assertThat(dp.allAppsLeftRightPadding).isEqualTo(64)
+    }
+
+    @Test
+    fun allAppsLeftRightMargin() {
+        assertThat(dp.allAppsLeftRightMargin).isEqualTo(1244)
+    }
+
+    @Test
+    fun hotseatBarSizePx() {
+        assertThat(dp.hotseatBarSizePx).isEqualTo(228)
+    }
+
+    @Test
+    fun hotseatCellHeightPx() {
+        assertThat(dp.hotseatCellHeightPx).isEqualTo(126)
+    }
+
+    @Test
+    fun hotseatBarBottomSpacePx() {
+        assertThat(dp.hotseatBarBottomSpacePx).isEqualTo(116)
+    }
+
+    @Test
+    fun hotseatBarSidePaddingStartPx() {
+        assertThat(dp.hotseatBarSidePaddingStartPx).isEqualTo(0)
+    }
+
+    @Test
+    fun hotseatBarSidePaddingEndPx() {
+        assertThat(dp.hotseatBarSidePaddingEndPx).isEqualTo(0)
+    }
+
+    @Test
+    fun hotseatQsbSpace() {
+        assertThat(dp.hotseatQsbSpace).isEqualTo(56)
+    }
+
+    @Test
+    fun hotseatQsbHeight() {
+        assertThat(dp.hotseatQsbHeight).isEqualTo(126)
+    }
+
+    @Test
+    fun springLoadedHotseatBarTopMarginPx() {
+        assertThat(dp.springLoadedHotseatBarTopMarginPx).isEqualTo(128)
+    }
+
+    @Test
+    fun numShownHotseatIcons() {
+        assertThat(dp.numShownHotseatIcons).isEqualTo(4)
+    }
+
+    @Test
+    fun hotseatBorderSpace() {
+        assertThat(dp.hotseatBorderSpace).isEqualTo(503)
+    }
+
+    @Test
+    fun isQsbInline() {
+        assertThat(dp.isQsbInline).isEqualTo(true)
+    }
+
+    @Test
+    fun qsbWidth() {
+        assertThat(dp.qsbWidth).isEqualTo(-503)
+    }
+
+    @Test
+    fun isTaskbarPresent() {
+        assertThat(dp.isTaskbarPresent).isEqualTo(true)
+    }
+
+    @Test
+    fun isTaskbarPresentInApps() {
+        assertThat(dp.isTaskbarPresentInApps).isEqualTo(false)
+    }
+
+    @Test
+    fun taskbarSize() {
+        assertThat(dp.taskbarSize).isEqualTo(120)
+    }
+
+    @Test
+    fun desiredWorkspaceHorizontalMarginPx() {
+        assertThat(dp.desiredWorkspaceHorizontalMarginPx).isEqualTo(78)
+    }
+
+    @Test
+    fun workspacePaddingLeft() {
+        assertThat(dp.workspacePadding.left).isEqualTo(19)
+    }
+
+    @Test
+    fun workspacePaddingTop() {
+        assertThat(dp.workspacePadding.top).isEqualTo(0)
+    }
+
+    @Test
+    fun workspacePaddingRight() {
+        assertThat(dp.workspacePadding.right).isEqualTo(19)
+    }
+
+    @Test
+    fun workspacePaddingBottom() {
+        assertThat(dp.workspacePadding.bottom).isEqualTo(93)
+    }
+
+    @Test
+    fun iconScale() {
+        assertThat(dp.iconScale).isEqualTo(1)
+    }
+
+    @Test
+    fun cellScaleToFit() {
+        assertThat(dp.cellScaleToFit).isEqualTo(1.7736843f)
+    }
+
+    @Test
+    fun workspaceTopPadding() {
+        assertThat(dp.workspaceTopPadding).isEqualTo(0)
+    }
+
+    @Test
+    fun workspaceBottomPadding() {
+        assertThat(dp.workspaceBottomPadding).isEqualTo(0)
+    }
+
+    @Test
+    fun overviewTaskMarginPx() {
+        assertThat(dp.overviewTaskMarginPx).isEqualTo(32)
+    }
+
+    @Test
+    fun overviewTaskMarginGridPx() {
+        assertThat(dp.overviewTaskMarginGridPx).isEqualTo(32)
+    }
+
+    @Test
+    fun overviewTaskIconSizePx() {
+        assertThat(dp.overviewTaskIconSizePx).isEqualTo(96)
+    }
+
+    @Test
+    fun overviewTaskIconDrawableSizePx() {
+        assertThat(dp.overviewTaskIconDrawableSizePx).isEqualTo(88)
+    }
+
+    @Test
+    fun overviewTaskIconDrawableSizeGridPx() {
+        assertThat(dp.overviewTaskIconDrawableSizeGridPx).isEqualTo(88)
+    }
+
+    @Test
+    fun overviewTaskThumbnailTopMarginPx() {
+        assertThat(dp.overviewTaskThumbnailTopMarginPx).isEqualTo(160)
+    }
+
+    @Test
+    fun overviewActionsTopMarginPx() {
+        assertThat(dp.overviewActionsTopMarginPx).isEqualTo(40)
+    }
+
+    @Test
+    fun overviewActionsHeight() {
+        assertThat(dp.overviewActionsHeight).isEqualTo(96)
+    }
+
+    @Test
+    fun overviewActionsButtonSpacing() {
+        assertThat(dp.overviewActionsButtonSpacing).isEqualTo(72)
+    }
+
+    @Test
+    fun overviewPageSpacing() {
+        assertThat(dp.overviewPageSpacing).isEqualTo(88)
+    }
+
+    @Test
+    fun overviewRowSpacing() {
+        assertThat(dp.overviewRowSpacing).isEqualTo(40)
+    }
+
+    @Test
+    fun overviewGridSideMargin() {
+        assertThat(dp.overviewGridSideMargin).isEqualTo(128)
+    }
+
+    @Test
+    fun dropTargetBarTopMarginPx() {
+        assertThat(dp.dropTargetBarTopMarginPx).isEqualTo(0)
+    }
+
+    @Test
+    fun dropTargetBarSizePx() {
+        assertThat(dp.dropTargetBarSizePx).isEqualTo(144)
+    }
+
+    @Test
+    fun dropTargetBarBottomMarginPx() {
+        assertThat(dp.dropTargetBarBottomMarginPx).isEqualTo(64)
+    }
+
+    @Test
+    fun workspaceSpringLoadedMinNextPageVisiblePx() {
+        assertThat(dp.workspaceSpringLoadedMinNextPageVisiblePx).isEqualTo(48)
+    }
+
+    @Test
+    fun getWorkspaceSpringLoadScale() {
+        assertThat(dp.workspaceSpringLoadScale).isEqualTo(0.7363184f)
+    }
+
+    @Test
+    fun getCellLayoutHeight() {
+        assertThat(dp.cellLayoutHeight).isEqualTo(1407)
+    }
+
+    @Test
+    fun getCellLayoutWidth() {
+        assertThat(dp.cellLayoutWidth).isEqualTo(2522)
+    }
+
+    @Test
+    fun getPanelCount() {
+        assertThat(dp.panelCount).isEqualTo(1)
+    }
+
+    @Test
+    fun isVerticalBarLayout() {
+        assertThat(dp.isVerticalBarLayout).isEqualTo(false)
+    }
+
+    @Test
+    fun getCellLayoutSpringLoadShrunkTop() {
+        assertThat(dp.cellLayoutSpringLoadShrunkTop).isEqualTo(208)
+    }
+
+    @Test
+    fun getCellLayoutSpringLoadShrunkBottom() {
+        assertThat(dp.cellLayoutSpringLoadShrunkBottom).isEqualTo(1244)
+    }
+
+    @Test
+    fun getQsbOffsetY() {
+        assertThat(dp.qsbOffsetY).isEqualTo(109)
+    }
+
+    @Test
+    fun getTaskbarOffsetY() {
+        assertThat(dp.taskbarOffsetY).isEqualTo(112)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingLeft() {
+        assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(301)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingTop() {
+        assertThat(dp.getHotseatLayoutPadding(context).top).isEqualTo(-7)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingRight() {
+        assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(301)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingBottom() {
+        assertThat(dp.getHotseatLayoutPadding(context).bottom).isEqualTo(109)
+    }
+
+    @Test
+    fun hotseatBarEndOffset() {
+        assertThat(dp.hotseatBarEndOffset).isEqualTo(0)
+    }
+
+    @Test
+    fun overviewGridRectLeft() {
+        assertThat(dp.overviewGridRect.left).isEqualTo(128)
+    }
+
+    @Test
+    fun overviewGridRectTop() {
+        assertThat(dp.overviewGridRect.top).isEqualTo(160)
+    }
+
+    @Test
+    fun overviewGridRectRight() {
+        assertThat(dp.overviewGridRect.right).isEqualTo(2432)
+    }
+
+    @Test
+    fun overviewGridRectBottom() {
+        assertThat(dp.overviewGridRect.bottom).isEqualTo(1416)
+    }
+
+    @Test
+    fun taskDimensionX() {
+        assertThat(dp.taskDimension.x).isEqualTo(2560)
+    }
+
+    @Test
+    fun taskDimensionY() {
+        assertThat(dp.taskDimension.y).isEqualTo(1480)
+    }
+
+    @Test
+    fun overviewTaskRectLeft() {
+        assertThat(dp.overviewTaskRect.left).isEqualTo(384)
+    }
+
+    @Test
+    fun overviewTaskRectTop() {
+        assertThat(dp.overviewTaskRect.top).isEqualTo(270)
+    }
+
+    @Test
+    fun overviewTaskRectRight() {
+        assertThat(dp.overviewTaskRect.right).isEqualTo(2176)
+    }
+
+    @Test
+    fun overviewTaskRectBottom() {
+        assertThat(dp.overviewTaskRect.bottom).isEqualTo(1306)
+    }
+
+    @Test
+    fun overviewGridTaskDimensionX() {
+        assertThat(dp.overviewGridTaskDimension.x).isEqualTo(723)
+    }
+
+    @Test
+    fun overviewGridTaskDimensionY() {
+        assertThat(dp.overviewGridTaskDimension.y).isEqualTo(418)
+    }
+
+    @Test
+    fun overviewModalTaskRectLeft() {
+        assertThat(dp.overviewModalTaskRect.left).isEqualTo(294)
+    }
+
+    @Test
+    fun overviewModalTaskRectTop() {
+        assertThat(dp.overviewModalTaskRect.top).isEqualTo(166)
+    }
+
+    @Test
+    fun overviewModalTaskRectRight() {
+        assertThat(dp.overviewModalTaskRect.right).isEqualTo(2266)
+    }
+
+    @Test
+    fun overviewModalTaskRectBottom() {
+        assertThat(dp.overviewModalTaskRect.bottom).isEqualTo(1306)
+    }
+
+    @Test
+    fun getGridTaskRectLeft() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).left).isEqualTo(1453)
+    }
+
+    @Test
+    fun getGridTaskRectTop() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).top).isEqualTo(270)
+    }
+
+    @Test
+    fun getGridTaskRectRight() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).right).isEqualTo(2176)
+    }
+
+    @Test
+    fun getGridTaskRectBottom() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).bottom).isEqualTo(688)
+    }
+
+    @Test
+    fun overviewTaskScale() {
+        assertThat(dp.overviewTaskWorkspaceScale).isEqualTo(0.7363184f)
+    }
+
+    @Test
+    fun overviewModalTaskScale() {
+        assertThat(dp.overviewModalTaskScale).isEqualTo(1.1003861f)
+    }
+}
\ No newline at end of file
diff --git a/quickstep/tests/src/com/android/quickstep/DeviceProfileTabletPortrait3ButtonTest.kt b/quickstep/tests/src/com/android/quickstep/DeviceProfileTabletPortrait3ButtonTest.kt
new file mode 100644
index 0000000..7f0d6ca
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/DeviceProfileTabletPortrait3ButtonTest.kt
@@ -0,0 +1,596 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.DeviceProfileBaseTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for DeviceProfile for tablet in portrait with 3-Button navigation.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceProfileTabletPortrait3ButtonTest : DeviceProfileBaseTest() {
+
+    lateinit var dp: DeviceProfile
+
+    @Before
+    fun before() {
+        initializeVarsForTablet(isGestureMode = false)
+        dp = newDP()
+    }
+
+    @Test
+    fun isScalableGrid() {
+        assertThat(dp.isScalableGrid).isTrue()
+    }
+
+    @Test
+    fun cellWidthPx() {
+        assertThat(dp.cellWidthPx).isEqualTo(294)
+    }
+
+    @Test
+    fun cellHeightPx() {
+        assertThat(dp.cellHeightPx).isEqualTo(382)
+    }
+
+    @Test
+    fun getCellSizeX() {
+        assertThat(dp.cellSize.x).isEqualTo(294)
+    }
+
+    @Test
+    fun getCellSizeY() {
+        assertThat(dp.cellSize.y).isEqualTo(482)
+    }
+
+    @Test
+    fun cellLayoutBorderSpacePxX() {
+        assertThat(dp.cellLayoutBorderSpacePx.x).isEqualTo(74)
+    }
+
+    @Test
+    fun cellLayoutBorderSpacePxY() {
+        assertThat(dp.cellLayoutBorderSpacePx.y).isEqualTo(74)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxLeft() {
+        assertThat(dp.cellLayoutPaddingPx.left).isEqualTo(72)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxTop() {
+        assertThat(dp.cellLayoutPaddingPx.top).isEqualTo(0)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxRight() {
+        assertThat(dp.cellLayoutPaddingPx.right).isEqualTo(72)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxBottom() {
+        assertThat(dp.cellLayoutPaddingPx.bottom).isEqualTo(72)
+    }
+
+    @Test
+    fun iconSizePx() {
+        assertThat(dp.iconSizePx).isEqualTo(112)
+    }
+
+    @Test
+    fun iconTextSizePx() {
+        assertThat(dp.iconTextSizePx).isEqualTo(28)
+    }
+
+    @Test
+    fun iconDrawablePaddingPx() {
+        assertThat(dp.iconDrawablePaddingPx).isEqualTo(14)
+    }
+
+    @Test
+    fun folderCellWidthPx() {
+        assertThat(dp.folderCellWidthPx).isEqualTo(294)
+    }
+
+    @Test
+    fun folderCellHeightPx() {
+        assertThat(dp.folderCellHeightPx).isEqualTo(382)
+    }
+
+    @Test
+    fun folderChildIconSizePx() {
+        assertThat(dp.folderChildIconSizePx).isEqualTo(112)
+    }
+
+    @Test
+    fun folderChildTextSizePx() {
+        assertThat(dp.folderChildTextSizePx).isEqualTo(28)
+    }
+
+    @Test
+    fun folderChildDrawablePaddingPx() {
+        assertThat(dp.folderChildDrawablePaddingPx).isEqualTo(77)
+    }
+
+    @Test
+    fun folderCellLayoutBorderSpaceOriginalPx() {
+        assertThat(dp.folderCellLayoutBorderSpaceOriginalPx).isEqualTo(0)
+    }
+
+    @Test
+    fun folderCellLayoutBorderSpacePxX() {
+        assertThat(dp.folderCellLayoutBorderSpacePx.x).isEqualTo(0)
+    }
+
+    @Test
+    fun folderCellLayoutBorderSpacePxY() {
+        assertThat(dp.folderCellLayoutBorderSpacePx.y).isEqualTo(0)
+    }
+
+    @Test
+    fun bottomSheetTopPadding() {
+        assertThat(dp.bottomSheetTopPadding).isEqualTo(600)
+    }
+
+    @Test
+    fun allAppsShiftRange() {
+        assertThat(dp.allAppsShiftRange).isEqualTo(1960)
+    }
+
+    @Test
+    fun allAppsTopPadding() {
+        assertThat(dp.allAppsTopPadding).isEqualTo(600)
+    }
+
+    @Test
+    fun allAppsIconSizePx() {
+        assertThat(dp.allAppsIconSizePx).isEqualTo(257)
+    }
+
+    @Test
+    fun allAppsIconTextSizePx() {
+        assertThat(dp.allAppsIconTextSizePx).isEqualTo(64)
+    }
+
+    @Test
+    fun allAppsIconDrawablePaddingPx() {
+        assertThat(dp.allAppsIconDrawablePaddingPx).isEqualTo(14)
+    }
+
+    @Test
+    fun allAppsCellHeightPx() {
+        assertThat(dp.allAppsCellHeightPx).isEqualTo(456)
+    }
+
+    @Test
+    fun allAppsCellWidthPx() {
+        assertThat(dp.allAppsCellWidthPx).isEqualTo(294)
+    }
+
+    @Test
+    fun allAppsBorderSpacePxX() {
+        assertThat(dp.allAppsBorderSpacePx.x).isEqualTo(74)
+    }
+
+    @Test
+    fun allAppsBorderSpacePxY() {
+        assertThat(dp.allAppsBorderSpacePx.y).isEqualTo(74)
+    }
+
+    @Test
+    fun numShownAllAppsColumns() {
+        assertThat(dp.numShownAllAppsColumns).isEqualTo(0)
+    }
+
+    @Test
+    fun allAppsLeftRightPadding() {
+        assertThat(dp.allAppsLeftRightPadding).isEqualTo(56)
+    }
+
+    @Test
+    fun allAppsLeftRightMargin() {
+        assertThat(dp.allAppsLeftRightMargin).isEqualTo(781)
+    }
+
+    @Test
+    fun hotseatBarSizePx() {
+        assertThat(dp.hotseatBarSizePx).isEqualTo(386)
+    }
+
+    @Test
+    fun hotseatCellHeightPx() {
+        assertThat(dp.hotseatCellHeightPx).isEqualTo(126)
+    }
+
+    @Test
+    fun hotseatBarBottomSpacePx() {
+        assertThat(dp.hotseatBarBottomSpacePx).isEqualTo(116)
+    }
+
+    @Test
+    fun hotseatBarSidePaddingStartPx() {
+        assertThat(dp.hotseatBarSidePaddingStartPx).isEqualTo(0)
+    }
+
+    @Test
+    fun hotseatBarSidePaddingEndPx() {
+        assertThat(dp.hotseatBarSidePaddingEndPx).isEqualTo(0)
+    }
+
+    @Test
+    fun hotseatQsbSpace() {
+        assertThat(dp.hotseatQsbSpace).isEqualTo(56)
+    }
+
+    @Test
+    fun hotseatQsbHeight() {
+        assertThat(dp.hotseatQsbHeight).isEqualTo(126)
+    }
+
+    @Test
+    fun springLoadedHotseatBarTopMarginPx() {
+        assertThat(dp.springLoadedHotseatBarTopMarginPx).isEqualTo(216)
+    }
+
+    @Test
+    fun numShownHotseatIcons() {
+        assertThat(dp.numShownHotseatIcons).isEqualTo(4)
+    }
+
+    @Test
+    fun hotseatBorderSpace() {
+        assertThat(dp.hotseatBorderSpace).isEqualTo(32)
+    }
+
+    @Test
+    fun isQsbInline() {
+        assertThat(dp.isQsbInline).isEqualTo(false)
+    }
+
+    @Test
+    fun qsbWidth() {
+        assertThat(dp.qsbWidth).isEqualTo(1216)
+    }
+
+    @Test
+    fun isTaskbarPresent() {
+        assertThat(dp.isTaskbarPresent).isEqualTo(true)
+    }
+
+    @Test
+    fun isTaskbarPresentInApps() {
+        assertThat(dp.isTaskbarPresentInApps).isEqualTo(false)
+    }
+
+    @Test
+    fun taskbarSize() {
+        assertThat(dp.taskbarSize).isEqualTo(120)
+    }
+
+    @Test
+    fun desiredWorkspaceHorizontalMarginPx() {
+        assertThat(dp.desiredWorkspaceHorizontalMarginPx).isEqualTo(101)
+    }
+
+    @Test
+    fun workspacePaddingLeft() {
+        assertThat(dp.workspacePadding.left).isEqualTo(29)
+    }
+
+    @Test
+    fun workspacePaddingTop() {
+        assertThat(dp.workspacePadding.top).isEqualTo(0)
+    }
+
+    @Test
+    fun workspacePaddingRight() {
+        assertThat(dp.workspacePadding.right).isEqualTo(29)
+    }
+
+    @Test
+    fun workspacePaddingBottom() {
+        assertThat(dp.workspacePadding.bottom).isEqualTo(238)
+    }
+
+    @Test
+    fun iconScale() {
+        assertThat(dp.iconScale).isEqualTo(1)
+    }
+
+    @Test
+    fun cellScaleToFit() {
+        assertThat(dp.cellScaleToFit).isEqualTo(2.2988505f)
+    }
+
+    @Test
+    fun workspaceTopPadding() {
+        assertThat(dp.workspaceTopPadding).isEqualTo(0)
+    }
+
+    @Test
+    fun workspaceBottomPadding() {
+        assertThat(dp.workspaceBottomPadding).isEqualTo(0)
+    }
+
+    @Test
+    fun overviewTaskMarginPx() {
+        assertThat(dp.overviewTaskMarginPx).isEqualTo(32)
+    }
+
+    @Test
+    fun overviewTaskMarginGridPx() {
+        assertThat(dp.overviewTaskMarginGridPx).isEqualTo(32)
+    }
+
+    @Test
+    fun overviewTaskIconSizePx() {
+        assertThat(dp.overviewTaskIconSizePx).isEqualTo(96)
+    }
+
+    @Test
+    fun overviewTaskIconDrawableSizePx() {
+        assertThat(dp.overviewTaskIconDrawableSizePx).isEqualTo(88)
+    }
+
+    @Test
+    fun overviewTaskIconDrawableSizeGridPx() {
+        assertThat(dp.overviewTaskIconDrawableSizeGridPx).isEqualTo(88)
+    }
+
+    @Test
+    fun overviewTaskThumbnailTopMarginPx() {
+        assertThat(dp.overviewTaskThumbnailTopMarginPx).isEqualTo(160)
+    }
+
+    @Test
+    fun overviewActionsTopMarginPx() {
+        assertThat(dp.overviewActionsTopMarginPx).isEqualTo(48)
+    }
+
+    @Test
+    fun overviewActionsHeight() {
+        assertThat(dp.overviewActionsHeight).isEqualTo(96)
+    }
+
+    @Test
+    fun overviewActionsButtonSpacing() {
+        assertThat(dp.overviewActionsButtonSpacing).isEqualTo(72)
+    }
+
+    @Test
+    fun overviewPageSpacing() {
+        assertThat(dp.overviewPageSpacing).isEqualTo(88)
+    }
+
+    @Test
+    fun overviewRowSpacing() {
+        assertThat(dp.overviewRowSpacing).isEqualTo(40)
+    }
+
+    @Test
+    fun overviewGridSideMargin() {
+        assertThat(dp.overviewGridSideMargin).isEqualTo(128)
+    }
+
+    @Test
+    fun dropTargetBarTopMarginPx() {
+        assertThat(dp.dropTargetBarTopMarginPx).isEqualTo(220)
+    }
+
+    @Test
+    fun dropTargetBarSizePx() {
+        assertThat(dp.dropTargetBarSizePx).isEqualTo(144)
+    }
+
+    @Test
+    fun dropTargetBarBottomMarginPx() {
+        assertThat(dp.dropTargetBarBottomMarginPx).isEqualTo(96)
+    }
+
+    @Test
+    fun workspaceSpringLoadedMinNextPageVisiblePx() {
+        assertThat(dp.workspaceSpringLoadedMinNextPageVisiblePx).isEqualTo(48)
+    }
+
+    @Test
+    fun getWorkspaceSpringLoadScale() {
+        assertThat(dp.workspaceSpringLoadScale).isEqualTo(0.6741674f)
+    }
+
+    @Test
+    fun getCellLayoutHeight() {
+        assertThat(dp.cellLayoutHeight).isEqualTo(2222)
+    }
+
+    @Test
+    fun getCellLayoutWidth() {
+        assertThat(dp.cellLayoutWidth).isEqualTo(1542)
+    }
+
+    @Test
+    fun getPanelCount() {
+        assertThat(dp.panelCount).isEqualTo(1)
+    }
+
+    @Test
+    fun isVerticalBarLayout() {
+        assertThat(dp.isVerticalBarLayout).isEqualTo(false)
+    }
+
+    @Test
+    fun getCellLayoutSpringLoadShrunkTop() {
+        assertThat(dp.cellLayoutSpringLoadShrunkTop).isEqualTo(460)
+    }
+
+    @Test
+    fun getCellLayoutSpringLoadShrunkBottom() {
+        assertThat(dp.cellLayoutSpringLoadShrunkBottom).isEqualTo(1958)
+    }
+
+    @Test
+    fun getQsbOffsetY() {
+        assertThat(dp.qsbOffsetY).isEqualTo(272)
+    }
+
+    @Test
+    fun getTaskbarOffsetY() {
+        assertThat(dp.taskbarOffsetY).isEqualTo(112)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingLeft() {
+        assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(528)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingTop() {
+        assertThat(dp.getHotseatLayoutPadding(context).top).isEqualTo(151)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingRight() {
+        assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(528)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingBottom() {
+        assertThat(dp.getHotseatLayoutPadding(context).bottom).isEqualTo(109)
+    }
+
+    @Test
+    fun hotseatBarEndOffset() {
+        assertThat(dp.hotseatBarEndOffset).isEqualTo(428)
+    }
+
+    @Test
+    fun overviewGridRectLeft() {
+        assertThat(dp.overviewGridRect.left).isEqualTo(128)
+    }
+
+    @Test
+    fun overviewGridRectTop() {
+        assertThat(dp.overviewGridRect.top).isEqualTo(160)
+    }
+
+    @Test
+    fun overviewGridRectRight() {
+        assertThat(dp.overviewGridRect.right).isEqualTo(1472)
+    }
+
+    @Test
+    fun overviewGridRectBottom() {
+        assertThat(dp.overviewGridRect.bottom).isEqualTo(2292)
+    }
+
+    @Test
+    fun taskDimensionX() {
+        assertThat(dp.taskDimension.x).isEqualTo(1600)
+    }
+
+    @Test
+    fun taskDimensionY() {
+        assertThat(dp.taskDimension.y).isEqualTo(2440)
+    }
+
+    @Test
+    fun overviewTaskRectLeft() {
+        assertThat(dp.overviewTaskRect.left).isEqualTo(240)
+    }
+
+    @Test
+    fun overviewTaskRectTop() {
+        assertThat(dp.overviewTaskRect.top).isEqualTo(372)
+    }
+
+    @Test
+    fun overviewTaskRectRight() {
+        assertThat(dp.overviewTaskRect.right).isEqualTo(1360)
+    }
+
+    @Test
+    fun overviewTaskRectBottom() {
+        assertThat(dp.overviewTaskRect.bottom).isEqualTo(2080)
+    }
+
+    @Test
+    fun overviewGridTaskDimensionX() {
+        assertThat(dp.overviewGridTaskDimension.x).isEqualTo(494)
+    }
+
+    @Test
+    fun overviewGridTaskDimensionY() {
+        assertThat(dp.overviewGridTaskDimension.y).isEqualTo(754)
+    }
+
+    @Test
+    fun overviewModalTaskRectLeft() {
+        assertThat(dp.overviewModalTaskRect.left).isEqualTo(184)
+    }
+
+    @Test
+    fun overviewModalTaskRectTop() {
+        assertThat(dp.overviewModalTaskRect.top).isEqualTo(201)
+    }
+
+    @Test
+    fun overviewModalTaskRectRight() {
+        assertThat(dp.overviewModalTaskRect.right).isEqualTo(1416)
+    }
+
+    @Test
+    fun overviewModalTaskRectBottom() {
+        assertThat(dp.overviewModalTaskRect.bottom).isEqualTo(2080)
+    }
+
+    @Test
+    fun getGridTaskRectLeft() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).left).isEqualTo(866)
+    }
+
+    @Test
+    fun getGridTaskRectTop() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).top).isEqualTo(372)
+    }
+
+    @Test
+    fun getGridTaskRectRight() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).right).isEqualTo(1360)
+    }
+
+    @Test
+    fun getGridTaskRectBottom() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).bottom).isEqualTo(1126)
+    }
+
+    @Test
+    fun overviewTaskScale() {
+        assertThat(dp.overviewTaskWorkspaceScale).isEqualTo(0.7686769f)
+    }
+
+    @Test
+    fun overviewModalTaskScale() {
+        assertThat(dp.overviewModalTaskScale).isEqualTo(1.1f)
+    }
+}
\ No newline at end of file
diff --git a/quickstep/tests/src/com/android/quickstep/DeviceProfileTabletPortraitTest.kt b/quickstep/tests/src/com/android/quickstep/DeviceProfileTabletPortraitTest.kt
new file mode 100644
index 0000000..1a709e6
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/DeviceProfileTabletPortraitTest.kt
@@ -0,0 +1,596 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.DeviceProfileBaseTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for DeviceProfile for tablet in portrait.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceProfileTabletPortraitTest : DeviceProfileBaseTest() {
+
+    lateinit var dp: DeviceProfile
+
+    @Before
+    fun before() {
+        initializeVarsForTablet()
+        dp = newDP()
+    }
+
+    @Test
+    fun isScalableGrid() {
+        assertThat(dp.isScalableGrid).isTrue()
+    }
+
+    @Test
+    fun cellWidthPx() {
+        assertThat(dp.cellWidthPx).isEqualTo(294)
+    }
+
+    @Test
+    fun cellHeightPx() {
+        assertThat(dp.cellHeightPx).isEqualTo(382)
+    }
+
+    @Test
+    fun getCellSizeX() {
+        assertThat(dp.cellSize.x).isEqualTo(294)
+    }
+
+    @Test
+    fun getCellSizeY() {
+        assertThat(dp.cellSize.y).isEqualTo(482)
+    }
+
+    @Test
+    fun cellLayoutBorderSpacePxX() {
+        assertThat(dp.cellLayoutBorderSpacePx.x).isEqualTo(74)
+    }
+
+    @Test
+    fun cellLayoutBorderSpacePxY() {
+        assertThat(dp.cellLayoutBorderSpacePx.y).isEqualTo(74)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxLeft() {
+        assertThat(dp.cellLayoutPaddingPx.left).isEqualTo(72)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxTop() {
+        assertThat(dp.cellLayoutPaddingPx.top).isEqualTo(0)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxRight() {
+        assertThat(dp.cellLayoutPaddingPx.right).isEqualTo(72)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxBottom() {
+        assertThat(dp.cellLayoutPaddingPx.bottom).isEqualTo(72)
+    }
+
+    @Test
+    fun iconSizePx() {
+        assertThat(dp.iconSizePx).isEqualTo(112)
+    }
+
+    @Test
+    fun iconTextSizePx() {
+        assertThat(dp.iconTextSizePx).isEqualTo(28)
+    }
+
+    @Test
+    fun iconDrawablePaddingPx() {
+        assertThat(dp.iconDrawablePaddingPx).isEqualTo(14)
+    }
+
+    @Test
+    fun folderCellWidthPx() {
+        assertThat(dp.folderCellWidthPx).isEqualTo(294)
+    }
+
+    @Test
+    fun folderCellHeightPx() {
+        assertThat(dp.folderCellHeightPx).isEqualTo(382)
+    }
+
+    @Test
+    fun folderChildIconSizePx() {
+        assertThat(dp.folderChildIconSizePx).isEqualTo(112)
+    }
+
+    @Test
+    fun folderChildTextSizePx() {
+        assertThat(dp.folderChildTextSizePx).isEqualTo(28)
+    }
+
+    @Test
+    fun folderChildDrawablePaddingPx() {
+        assertThat(dp.folderChildDrawablePaddingPx).isEqualTo(77)
+    }
+
+    @Test
+    fun folderCellLayoutBorderSpaceOriginalPx() {
+        assertThat(dp.folderCellLayoutBorderSpaceOriginalPx).isEqualTo(0)
+    }
+
+    @Test
+    fun folderCellLayoutBorderSpacePxX() {
+        assertThat(dp.folderCellLayoutBorderSpacePx.x).isEqualTo(0)
+    }
+
+    @Test
+    fun folderCellLayoutBorderSpacePxY() {
+        assertThat(dp.folderCellLayoutBorderSpacePx.y).isEqualTo(0)
+    }
+
+    @Test
+    fun bottomSheetTopPadding() {
+        assertThat(dp.bottomSheetTopPadding).isEqualTo(600)
+    }
+
+    @Test
+    fun allAppsShiftRange() {
+        assertThat(dp.allAppsShiftRange).isEqualTo(1960)
+    }
+
+    @Test
+    fun allAppsTopPadding() {
+        assertThat(dp.allAppsTopPadding).isEqualTo(600)
+    }
+
+    @Test
+    fun allAppsIconSizePx() {
+        assertThat(dp.allAppsIconSizePx).isEqualTo(257)
+    }
+
+    @Test
+    fun allAppsIconTextSizePx() {
+        assertThat(dp.allAppsIconTextSizePx).isEqualTo(64)
+    }
+
+    @Test
+    fun allAppsIconDrawablePaddingPx() {
+        assertThat(dp.allAppsIconDrawablePaddingPx).isEqualTo(14)
+    }
+
+    @Test
+    fun allAppsCellHeightPx() {
+        assertThat(dp.allAppsCellHeightPx).isEqualTo(456)
+    }
+
+    @Test
+    fun allAppsCellWidthPx() {
+        assertThat(dp.allAppsCellWidthPx).isEqualTo(294)
+    }
+
+    @Test
+    fun allAppsBorderSpacePxX() {
+        assertThat(dp.allAppsBorderSpacePx.x).isEqualTo(74)
+    }
+
+    @Test
+    fun allAppsBorderSpacePxY() {
+        assertThat(dp.allAppsBorderSpacePx.y).isEqualTo(74)
+    }
+
+    @Test
+    fun numShownAllAppsColumns() {
+        assertThat(dp.numShownAllAppsColumns).isEqualTo(0)
+    }
+
+    @Test
+    fun allAppsLeftRightPadding() {
+        assertThat(dp.allAppsLeftRightPadding).isEqualTo(56)
+    }
+
+    @Test
+    fun allAppsLeftRightMargin() {
+        assertThat(dp.allAppsLeftRightMargin).isEqualTo(781)
+    }
+
+    @Test
+    fun hotseatBarSizePx() {
+        assertThat(dp.hotseatBarSizePx).isEqualTo(386)
+    }
+
+    @Test
+    fun hotseatCellHeightPx() {
+        assertThat(dp.hotseatCellHeightPx).isEqualTo(126)
+    }
+
+    @Test
+    fun hotseatBarBottomSpacePx() {
+        assertThat(dp.hotseatBarBottomSpacePx).isEqualTo(116)
+    }
+
+    @Test
+    fun hotseatBarSidePaddingStartPx() {
+        assertThat(dp.hotseatBarSidePaddingStartPx).isEqualTo(0)
+    }
+
+    @Test
+    fun hotseatBarSidePaddingEndPx() {
+        assertThat(dp.hotseatBarSidePaddingEndPx).isEqualTo(0)
+    }
+
+    @Test
+    fun hotseatQsbSpace() {
+        assertThat(dp.hotseatQsbSpace).isEqualTo(56)
+    }
+
+    @Test
+    fun hotseatQsbHeight() {
+        assertThat(dp.hotseatQsbHeight).isEqualTo(126)
+    }
+
+    @Test
+    fun springLoadedHotseatBarTopMarginPx() {
+        assertThat(dp.springLoadedHotseatBarTopMarginPx).isEqualTo(216)
+    }
+
+    @Test
+    fun numShownHotseatIcons() {
+        assertThat(dp.numShownHotseatIcons).isEqualTo(4)
+    }
+
+    @Test
+    fun hotseatBorderSpace() {
+        assertThat(dp.hotseatBorderSpace).isEqualTo(256)
+    }
+
+    @Test
+    fun isQsbInline() {
+        assertThat(dp.isQsbInline).isEqualTo(false)
+    }
+
+    @Test
+    fun qsbWidth() {
+        assertThat(dp.qsbWidth).isEqualTo(1216)
+    }
+
+    @Test
+    fun isTaskbarPresent() {
+        assertThat(dp.isTaskbarPresent).isEqualTo(true)
+    }
+
+    @Test
+    fun isTaskbarPresentInApps() {
+        assertThat(dp.isTaskbarPresentInApps).isEqualTo(false)
+    }
+
+    @Test
+    fun taskbarSize() {
+        assertThat(dp.taskbarSize).isEqualTo(120)
+    }
+
+    @Test
+    fun desiredWorkspaceHorizontalMarginPx() {
+        assertThat(dp.desiredWorkspaceHorizontalMarginPx).isEqualTo(101)
+    }
+
+    @Test
+    fun workspacePaddingLeft() {
+        assertThat(dp.workspacePadding.left).isEqualTo(29)
+    }
+
+    @Test
+    fun workspacePaddingTop() {
+        assertThat(dp.workspacePadding.top).isEqualTo(0)
+    }
+
+    @Test
+    fun workspacePaddingRight() {
+        assertThat(dp.workspacePadding.right).isEqualTo(29)
+    }
+
+    @Test
+    fun workspacePaddingBottom() {
+        assertThat(dp.workspacePadding.bottom).isEqualTo(238)
+    }
+
+    @Test
+    fun iconScale() {
+        assertThat(dp.iconScale).isEqualTo(1)
+    }
+
+    @Test
+    fun cellScaleToFit() {
+        assertThat(dp.cellScaleToFit).isEqualTo(2.2988505f)
+    }
+
+    @Test
+    fun workspaceTopPadding() {
+        assertThat(dp.workspaceTopPadding).isEqualTo(0)
+    }
+
+    @Test
+    fun workspaceBottomPadding() {
+        assertThat(dp.workspaceBottomPadding).isEqualTo(0)
+    }
+
+    @Test
+    fun overviewTaskMarginPx() {
+        assertThat(dp.overviewTaskMarginPx).isEqualTo(32)
+    }
+
+    @Test
+    fun overviewTaskMarginGridPx() {
+        assertThat(dp.overviewTaskMarginGridPx).isEqualTo(32)
+    }
+
+    @Test
+    fun overviewTaskIconSizePx() {
+        assertThat(dp.overviewTaskIconSizePx).isEqualTo(96)
+    }
+
+    @Test
+    fun overviewTaskIconDrawableSizePx() {
+        assertThat(dp.overviewTaskIconDrawableSizePx).isEqualTo(88)
+    }
+
+    @Test
+    fun overviewTaskIconDrawableSizeGridPx() {
+        assertThat(dp.overviewTaskIconDrawableSizeGridPx).isEqualTo(88)
+    }
+
+    @Test
+    fun overviewTaskThumbnailTopMarginPx() {
+        assertThat(dp.overviewTaskThumbnailTopMarginPx).isEqualTo(160)
+    }
+
+    @Test
+    fun overviewActionsTopMarginPx() {
+        assertThat(dp.overviewActionsTopMarginPx).isEqualTo(48)
+    }
+
+    @Test
+    fun overviewActionsHeight() {
+        assertThat(dp.overviewActionsHeight).isEqualTo(96)
+    }
+
+    @Test
+    fun overviewActionsButtonSpacing() {
+        assertThat(dp.overviewActionsButtonSpacing).isEqualTo(72)
+    }
+
+    @Test
+    fun overviewPageSpacing() {
+        assertThat(dp.overviewPageSpacing).isEqualTo(88)
+    }
+
+    @Test
+    fun overviewRowSpacing() {
+        assertThat(dp.overviewRowSpacing).isEqualTo(40)
+    }
+
+    @Test
+    fun overviewGridSideMargin() {
+        assertThat(dp.overviewGridSideMargin).isEqualTo(128)
+    }
+
+    @Test
+    fun dropTargetBarTopMarginPx() {
+        assertThat(dp.dropTargetBarTopMarginPx).isEqualTo(220)
+    }
+
+    @Test
+    fun dropTargetBarSizePx() {
+        assertThat(dp.dropTargetBarSizePx).isEqualTo(144)
+    }
+
+    @Test
+    fun dropTargetBarBottomMarginPx() {
+        assertThat(dp.dropTargetBarBottomMarginPx).isEqualTo(96)
+    }
+
+    @Test
+    fun workspaceSpringLoadedMinNextPageVisiblePx() {
+        assertThat(dp.workspaceSpringLoadedMinNextPageVisiblePx).isEqualTo(48)
+    }
+
+    @Test
+    fun getWorkspaceSpringLoadScale() {
+        assertThat(dp.workspaceSpringLoadScale).isEqualTo(0.6741674f)
+    }
+
+    @Test
+    fun getCellLayoutHeight() {
+        assertThat(dp.cellLayoutHeight).isEqualTo(2222)
+    }
+
+    @Test
+    fun getCellLayoutWidth() {
+        assertThat(dp.cellLayoutWidth).isEqualTo(1542)
+    }
+
+    @Test
+    fun getPanelCount() {
+        assertThat(dp.panelCount).isEqualTo(1)
+    }
+
+    @Test
+    fun isVerticalBarLayout() {
+        assertThat(dp.isVerticalBarLayout).isEqualTo(false)
+    }
+
+    @Test
+    fun getCellLayoutSpringLoadShrunkTop() {
+        assertThat(dp.cellLayoutSpringLoadShrunkTop).isEqualTo(460)
+    }
+
+    @Test
+    fun getCellLayoutSpringLoadShrunkBottom() {
+        assertThat(dp.cellLayoutSpringLoadShrunkBottom).isEqualTo(1958)
+    }
+
+    @Test
+    fun getQsbOffsetY() {
+        assertThat(dp.qsbOffsetY).isEqualTo(272)
+    }
+
+    @Test
+    fun getTaskbarOffsetY() {
+        assertThat(dp.taskbarOffsetY).isEqualTo(112)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingLeft() {
+        assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(192)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingTop() {
+        assertThat(dp.getHotseatLayoutPadding(context).top).isEqualTo(151)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingRight() {
+        assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(192)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingBottom() {
+        assertThat(dp.getHotseatLayoutPadding(context).bottom).isEqualTo(109)
+    }
+
+    @Test
+    fun hotseatBarEndOffset() {
+        assertThat(dp.hotseatBarEndOffset).isEqualTo(0)
+    }
+
+    @Test
+    fun overviewGridRectLeft() {
+        assertThat(dp.overviewGridRect.left).isEqualTo(128)
+    }
+
+    @Test
+    fun overviewGridRectTop() {
+        assertThat(dp.overviewGridRect.top).isEqualTo(160)
+    }
+
+    @Test
+    fun overviewGridRectRight() {
+        assertThat(dp.overviewGridRect.right).isEqualTo(1472)
+    }
+
+    @Test
+    fun overviewGridRectBottom() {
+        assertThat(dp.overviewGridRect.bottom).isEqualTo(2368)
+    }
+
+    @Test
+    fun taskDimensionX() {
+        assertThat(dp.taskDimension.x).isEqualTo(1600)
+    }
+
+    @Test
+    fun taskDimensionY() {
+        assertThat(dp.taskDimension.y).isEqualTo(2440)
+    }
+
+    @Test
+    fun overviewTaskRectLeft() {
+        assertThat(dp.overviewTaskRect.left).isEqualTo(240)
+    }
+
+    @Test
+    fun overviewTaskRectTop() {
+        assertThat(dp.overviewTaskRect.top).isEqualTo(410)
+    }
+
+    @Test
+    fun overviewTaskRectRight() {
+        assertThat(dp.overviewTaskRect.right).isEqualTo(1360)
+    }
+
+    @Test
+    fun overviewTaskRectBottom() {
+        assertThat(dp.overviewTaskRect.bottom).isEqualTo(2118)
+    }
+
+    @Test
+    fun overviewGridTaskDimensionX() {
+        assertThat(dp.overviewGridTaskDimension.x).isEqualTo(494)
+    }
+
+    @Test
+    fun overviewGridTaskDimensionY() {
+        assertThat(dp.overviewGridTaskDimension.y).isEqualTo(754)
+    }
+
+    @Test
+    fun overviewModalTaskRectLeft() {
+        assertThat(dp.overviewModalTaskRect.left).isEqualTo(184)
+    }
+
+    @Test
+    fun overviewModalTaskRectTop() {
+        assertThat(dp.overviewModalTaskRect.top).isEqualTo(239)
+    }
+
+    @Test
+    fun overviewModalTaskRectRight() {
+        assertThat(dp.overviewModalTaskRect.right).isEqualTo(1416)
+    }
+
+    @Test
+    fun overviewModalTaskRectBottom() {
+        assertThat(dp.overviewModalTaskRect.bottom).isEqualTo(2118)
+    }
+
+    @Test
+    fun getGridTaskRectLeft() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).left).isEqualTo(866)
+    }
+
+    @Test
+    fun getGridTaskRectTop() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).top).isEqualTo(410)
+    }
+
+    @Test
+    fun getGridTaskRectRight() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).right).isEqualTo(1360)
+    }
+
+    @Test
+    fun getGridTaskRectBottom() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).bottom).isEqualTo(1164)
+    }
+
+    @Test
+    fun overviewTaskScale() {
+        assertThat(dp.overviewTaskWorkspaceScale).isEqualTo(0.7686769f)
+    }
+
+    @Test
+    fun overviewModalTaskScale() {
+        assertThat(dp.overviewModalTaskScale).isEqualTo(1.1f)
+    }
+}
\ No newline at end of file
diff --git a/quickstep/tests/src/com/android/quickstep/DeviceProfileTwoPanelLandscape3ButtonTest.kt b/quickstep/tests/src/com/android/quickstep/DeviceProfileTwoPanelLandscape3ButtonTest.kt
new file mode 100644
index 0000000..0eeb23d
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/DeviceProfileTwoPanelLandscape3ButtonTest.kt
@@ -0,0 +1,596 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.DeviceProfileBaseTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for DeviceProfile for two panel in landscape with 3-Button navigation.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceProfileTwoPanelLandscape3ButtonTest : DeviceProfileBaseTest() {
+
+    lateinit var dp: DeviceProfile
+
+    @Before
+    fun before() {
+        initializeVarsForTablet(isLandscape = true, isTwoPanel = true, isGestureMode = false)
+        dp = newDP()
+    }
+
+    @Test
+    fun isScalableGrid() {
+        assertThat(dp.isScalableGrid).isTrue()
+    }
+
+    @Test
+    fun cellWidthPx() {
+        assertThat(dp.cellWidthPx).isEqualTo(200)
+    }
+
+    @Test
+    fun cellHeightPx() {
+        assertThat(dp.cellHeightPx).isEqualTo(260)
+    }
+
+    @Test
+    fun getCellSizeX() {
+        assertThat(dp.cellSize.x).isEqualTo(259)
+    }
+
+    @Test
+    fun getCellSizeY() {
+        assertThat(dp.cellSize.y).isEqualTo(260)
+    }
+
+    @Test
+    fun cellLayoutBorderSpacePxX() {
+        assertThat(dp.cellLayoutBorderSpacePx.x).isEqualTo(50)
+    }
+
+    @Test
+    fun cellLayoutBorderSpacePxY() {
+        assertThat(dp.cellLayoutBorderSpacePx.y).isEqualTo(50)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxLeft() {
+        assertThat(dp.cellLayoutPaddingPx.left).isEqualTo(25)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxTop() {
+        assertThat(dp.cellLayoutPaddingPx.top).isEqualTo(0)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxRight() {
+        assertThat(dp.cellLayoutPaddingPx.right).isEqualTo(25)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxBottom() {
+        assertThat(dp.cellLayoutPaddingPx.bottom).isEqualTo(25)
+    }
+
+    @Test
+    fun iconSizePx() {
+        assertThat(dp.iconSizePx).isEqualTo(112)
+    }
+
+    @Test
+    fun iconTextSizePx() {
+        assertThat(dp.iconTextSizePx).isEqualTo(28)
+    }
+
+    @Test
+    fun iconDrawablePaddingPx() {
+        assertThat(dp.iconDrawablePaddingPx).isEqualTo(14)
+    }
+
+    @Test
+    fun folderCellWidthPx() {
+        assertThat(dp.folderCellWidthPx).isEqualTo(200)
+    }
+
+    @Test
+    fun folderCellHeightPx() {
+        assertThat(dp.folderCellHeightPx).isEqualTo(260)
+    }
+
+    @Test
+    fun folderChildIconSizePx() {
+        assertThat(dp.folderChildIconSizePx).isEqualTo(112)
+    }
+
+    @Test
+    fun folderChildTextSizePx() {
+        assertThat(dp.folderChildTextSizePx).isEqualTo(28)
+    }
+
+    @Test
+    fun folderChildDrawablePaddingPx() {
+        assertThat(dp.folderChildDrawablePaddingPx).isEqualTo(36)
+    }
+
+    @Test
+    fun folderCellLayoutBorderSpaceOriginalPx() {
+        assertThat(dp.folderCellLayoutBorderSpaceOriginalPx).isEqualTo(0)
+    }
+
+    @Test
+    fun folderCellLayoutBorderSpacePxX() {
+        assertThat(dp.folderCellLayoutBorderSpacePx.x).isEqualTo(0)
+    }
+
+    @Test
+    fun folderCellLayoutBorderSpacePxY() {
+        assertThat(dp.folderCellLayoutBorderSpacePx.y).isEqualTo(0)
+    }
+
+    @Test
+    fun bottomSheetTopPadding() {
+        assertThat(dp.bottomSheetTopPadding).isEqualTo(0)
+    }
+
+    @Test
+    fun allAppsShiftRange() {
+        assertThat(dp.allAppsShiftRange).isEqualTo(1600)
+    }
+
+    @Test
+    fun allAppsTopPadding() {
+        assertThat(dp.allAppsTopPadding).isEqualTo(0)
+    }
+
+    @Test
+    fun allAppsIconSizePx() {
+        assertThat(dp.allAppsIconSizePx).isEqualTo(175)
+    }
+
+    @Test
+    fun allAppsIconTextSizePx() {
+        assertThat(dp.allAppsIconTextSizePx).isEqualTo(44)
+    }
+
+    @Test
+    fun allAppsIconDrawablePaddingPx() {
+        assertThat(dp.allAppsIconDrawablePaddingPx).isEqualTo(14)
+    }
+
+    @Test
+    fun allAppsCellHeightPx() {
+        assertThat(dp.allAppsCellHeightPx).isEqualTo(310)
+    }
+
+    @Test
+    fun allAppsCellWidthPx() {
+        assertThat(dp.allAppsCellWidthPx).isEqualTo(200)
+    }
+
+    @Test
+    fun allAppsBorderSpacePxX() {
+        assertThat(dp.allAppsBorderSpacePx.x).isEqualTo(50)
+    }
+
+    @Test
+    fun allAppsBorderSpacePxY() {
+        assertThat(dp.allAppsBorderSpacePx.y).isEqualTo(50)
+    }
+
+    @Test
+    fun numShownAllAppsColumns() {
+        assertThat(dp.numShownAllAppsColumns).isEqualTo(0)
+    }
+
+    @Test
+    fun allAppsLeftRightPadding() {
+        assertThat(dp.allAppsLeftRightPadding).isEqualTo(64)
+    }
+
+    @Test
+    fun allAppsLeftRightMargin() {
+        assertThat(dp.allAppsLeftRightMargin).isEqualTo(1241)
+    }
+
+    @Test
+    fun hotseatBarSizePx() {
+        assertThat(dp.hotseatBarSizePx).isEqualTo(386)
+    }
+
+    @Test
+    fun hotseatCellHeightPx() {
+        assertThat(dp.hotseatCellHeightPx).isEqualTo(126)
+    }
+
+    @Test
+    fun hotseatBarBottomSpacePx() {
+        assertThat(dp.hotseatBarBottomSpacePx).isEqualTo(116)
+    }
+
+    @Test
+    fun hotseatBarSidePaddingStartPx() {
+        assertThat(dp.hotseatBarSidePaddingStartPx).isEqualTo(0)
+    }
+
+    @Test
+    fun hotseatBarSidePaddingEndPx() {
+        assertThat(dp.hotseatBarSidePaddingEndPx).isEqualTo(0)
+    }
+
+    @Test
+    fun hotseatQsbSpace() {
+        assertThat(dp.hotseatQsbSpace).isEqualTo(56)
+    }
+
+    @Test
+    fun hotseatQsbHeight() {
+        assertThat(dp.hotseatQsbHeight).isEqualTo(126)
+    }
+
+    @Test
+    fun springLoadedHotseatBarTopMarginPx() {
+        assertThat(dp.springLoadedHotseatBarTopMarginPx).isEqualTo(128)
+    }
+
+    @Test
+    fun numShownHotseatIcons() {
+        assertThat(dp.numShownHotseatIcons).isEqualTo(6)
+    }
+
+    @Test
+    fun hotseatBorderSpace() {
+        assertThat(dp.hotseatBorderSpace).isEqualTo(32)
+    }
+
+    @Test
+    fun isQsbInline() {
+        assertThat(dp.isQsbInline).isEqualTo(false)
+    }
+
+    @Test
+    fun qsbWidth() {
+        assertThat(dp.qsbWidth).isEqualTo(1039)
+    }
+
+    @Test
+    fun isTaskbarPresent() {
+        assertThat(dp.isTaskbarPresent).isEqualTo(true)
+    }
+
+    @Test
+    fun isTaskbarPresentInApps() {
+        assertThat(dp.isTaskbarPresentInApps).isEqualTo(false)
+    }
+
+    @Test
+    fun taskbarSize() {
+        assertThat(dp.taskbarSize).isEqualTo(120)
+    }
+
+    @Test
+    fun desiredWorkspaceHorizontalMarginPx() {
+        assertThat(dp.desiredWorkspaceHorizontalMarginPx).isEqualTo(68)
+    }
+
+    @Test
+    fun workspacePaddingLeft() {
+        assertThat(dp.workspacePadding.left).isEqualTo(43)
+    }
+
+    @Test
+    fun workspacePaddingTop() {
+        assertThat(dp.workspacePadding.top).isEqualTo(0)
+    }
+
+    @Test
+    fun workspacePaddingRight() {
+        assertThat(dp.workspacePadding.right).isEqualTo(43)
+    }
+
+    @Test
+    fun workspacePaddingBottom() {
+        assertThat(dp.workspacePadding.bottom).isEqualTo(285)
+    }
+
+    @Test
+    fun iconScale() {
+        assertThat(dp.iconScale).isEqualTo(1)
+    }
+
+    @Test
+    fun cellScaleToFit() {
+        assertThat(dp.cellScaleToFit).isEqualTo(1.5657895f)
+    }
+
+    @Test
+    fun workspaceTopPadding() {
+        assertThat(dp.workspaceTopPadding).isEqualTo(0)
+    }
+
+    @Test
+    fun workspaceBottomPadding() {
+        assertThat(dp.workspaceBottomPadding).isEqualTo(0)
+    }
+
+    @Test
+    fun overviewTaskMarginPx() {
+        assertThat(dp.overviewTaskMarginPx).isEqualTo(32)
+    }
+
+    @Test
+    fun overviewTaskMarginGridPx() {
+        assertThat(dp.overviewTaskMarginGridPx).isEqualTo(32)
+    }
+
+    @Test
+    fun overviewTaskIconSizePx() {
+        assertThat(dp.overviewTaskIconSizePx).isEqualTo(96)
+    }
+
+    @Test
+    fun overviewTaskIconDrawableSizePx() {
+        assertThat(dp.overviewTaskIconDrawableSizePx).isEqualTo(88)
+    }
+
+    @Test
+    fun overviewTaskIconDrawableSizeGridPx() {
+        assertThat(dp.overviewTaskIconDrawableSizeGridPx).isEqualTo(88)
+    }
+
+    @Test
+    fun overviewTaskThumbnailTopMarginPx() {
+        assertThat(dp.overviewTaskThumbnailTopMarginPx).isEqualTo(160)
+    }
+
+    @Test
+    fun overviewActionsTopMarginPx() {
+        assertThat(dp.overviewActionsTopMarginPx).isEqualTo(40)
+    }
+
+    @Test
+    fun overviewActionsHeight() {
+        assertThat(dp.overviewActionsHeight).isEqualTo(96)
+    }
+
+    @Test
+    fun overviewActionsButtonSpacing() {
+        assertThat(dp.overviewActionsButtonSpacing).isEqualTo(72)
+    }
+
+    @Test
+    fun overviewPageSpacing() {
+        assertThat(dp.overviewPageSpacing).isEqualTo(88)
+    }
+
+    @Test
+    fun overviewRowSpacing() {
+        assertThat(dp.overviewRowSpacing).isEqualTo(40)
+    }
+
+    @Test
+    fun overviewGridSideMargin() {
+        assertThat(dp.overviewGridSideMargin).isEqualTo(128)
+    }
+
+    @Test
+    fun dropTargetBarTopMarginPx() {
+        assertThat(dp.dropTargetBarTopMarginPx).isEqualTo(0)
+    }
+
+    @Test
+    fun dropTargetBarSizePx() {
+        assertThat(dp.dropTargetBarSizePx).isEqualTo(144)
+    }
+
+    @Test
+    fun dropTargetBarBottomMarginPx() {
+        assertThat(dp.dropTargetBarBottomMarginPx).isEqualTo(64)
+    }
+
+    @Test
+    fun workspaceSpringLoadedMinNextPageVisiblePx() {
+        assertThat(dp.workspaceSpringLoadedMinNextPageVisiblePx).isEqualTo(48)
+    }
+
+    @Test
+    fun getWorkspaceSpringLoadScale() {
+        assertThat(dp.workspaceSpringLoadScale).isEqualTo(0.7226337f)
+    }
+
+    @Test
+    fun getCellLayoutHeight() {
+        assertThat(dp.cellLayoutHeight).isEqualTo(1215)
+    }
+
+    @Test
+    fun getCellLayoutWidth() {
+        assertThat(dp.cellLayoutWidth).isEqualTo(1237)
+    }
+
+    @Test
+    fun getPanelCount() {
+        assertThat(dp.panelCount).isEqualTo(2)
+    }
+
+    @Test
+    fun isVerticalBarLayout() {
+        assertThat(dp.isVerticalBarLayout).isEqualTo(false)
+    }
+
+    @Test
+    fun getCellLayoutSpringLoadShrunkTop() {
+        assertThat(dp.cellLayoutSpringLoadShrunkTop).isEqualTo(208)
+    }
+
+    @Test
+    fun getCellLayoutSpringLoadShrunkBottom() {
+        assertThat(dp.cellLayoutSpringLoadShrunkBottom).isEqualTo(1086)
+    }
+
+    @Test
+    fun getQsbOffsetY() {
+        assertThat(dp.qsbOffsetY).isEqualTo(272)
+    }
+
+    @Test
+    fun getTaskbarOffsetY() {
+        assertThat(dp.taskbarOffsetY).isEqualTo(112)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingLeft() {
+        assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(864)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingTop() {
+        assertThat(dp.getHotseatLayoutPadding(context).top).isEqualTo(151)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingRight() {
+        assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(864)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingBottom() {
+        assertThat(dp.getHotseatLayoutPadding(context).bottom).isEqualTo(109)
+    }
+
+    @Test
+    fun hotseatBarEndOffset() {
+        assertThat(dp.hotseatBarEndOffset).isEqualTo(428)
+    }
+
+    @Test
+    fun overviewGridRectLeft() {
+        assertThat(dp.overviewGridRect.left).isEqualTo(128)
+    }
+
+    @Test
+    fun overviewGridRectTop() {
+        assertThat(dp.overviewGridRect.top).isEqualTo(160)
+    }
+
+    @Test
+    fun overviewGridRectRight() {
+        assertThat(dp.overviewGridRect.right).isEqualTo(2432)
+    }
+
+    @Test
+    fun overviewGridRectBottom() {
+        assertThat(dp.overviewGridRect.bottom).isEqualTo(1340)
+    }
+
+    @Test
+    fun taskDimensionX() {
+        assertThat(dp.taskDimension.x).isEqualTo(2560)
+    }
+
+    @Test
+    fun taskDimensionY() {
+        assertThat(dp.taskDimension.y).isEqualTo(1480)
+    }
+
+    @Test
+    fun overviewTaskRectLeft() {
+        assertThat(dp.overviewTaskRect.left).isEqualTo(384)
+    }
+
+    @Test
+    fun overviewTaskRectTop() {
+        assertThat(dp.overviewTaskRect.top).isEqualTo(232)
+    }
+
+    @Test
+    fun overviewTaskRectRight() {
+        assertThat(dp.overviewTaskRect.right).isEqualTo(2176)
+    }
+
+    @Test
+    fun overviewTaskRectBottom() {
+        assertThat(dp.overviewTaskRect.bottom).isEqualTo(1268)
+    }
+
+    @Test
+    fun overviewGridTaskDimensionX() {
+        assertThat(dp.overviewGridTaskDimension.x).isEqualTo(723)
+    }
+
+    @Test
+    fun overviewGridTaskDimensionY() {
+        assertThat(dp.overviewGridTaskDimension.y).isEqualTo(418)
+    }
+
+    @Test
+    fun overviewModalTaskRectLeft() {
+        assertThat(dp.overviewModalTaskRect.left).isEqualTo(294)
+    }
+
+    @Test
+    fun overviewModalTaskRectTop() {
+        assertThat(dp.overviewModalTaskRect.top).isEqualTo(128)
+    }
+
+    @Test
+    fun overviewModalTaskRectRight() {
+        assertThat(dp.overviewModalTaskRect.right).isEqualTo(2266)
+    }
+
+    @Test
+    fun overviewModalTaskRectBottom() {
+        assertThat(dp.overviewModalTaskRect.bottom).isEqualTo(1268)
+    }
+
+    @Test
+    fun getGridTaskRectLeft() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).left).isEqualTo(1453)
+    }
+
+    @Test
+    fun getGridTaskRectTop() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).top).isEqualTo(232)
+    }
+
+    @Test
+    fun getGridTaskRectRight() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).right).isEqualTo(2176)
+    }
+
+    @Test
+    fun getGridTaskRectBottom() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).bottom).isEqualTo(650)
+    }
+
+    @Test
+    fun overviewTaskScale() {
+        assertThat(dp.overviewTaskWorkspaceScale).isEqualTo(0.8526749f)
+    }
+
+    @Test
+    fun overviewModalTaskScale() {
+        assertThat(dp.overviewModalTaskScale).isEqualTo(1.1003861f)
+    }
+}
\ No newline at end of file
diff --git a/quickstep/tests/src/com/android/quickstep/DeviceProfileTwoPanelLandscapeTest.kt b/quickstep/tests/src/com/android/quickstep/DeviceProfileTwoPanelLandscapeTest.kt
new file mode 100644
index 0000000..7bf53d7
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/DeviceProfileTwoPanelLandscapeTest.kt
@@ -0,0 +1,596 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.DeviceProfileBaseTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for DeviceProfile for two panel in landscape.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceProfileTwoPanelLandscapeTest : DeviceProfileBaseTest() {
+
+    lateinit var dp: DeviceProfile
+
+    @Before
+    fun before() {
+        initializeVarsForTablet(isLandscape = true, isTwoPanel = true)
+        dp = newDP()
+    }
+
+    @Test
+    fun isScalableGrid() {
+        assertThat(dp.isScalableGrid).isTrue()
+    }
+
+    @Test
+    fun cellWidthPx() {
+        assertThat(dp.cellWidthPx).isEqualTo(200)
+    }
+
+    @Test
+    fun cellHeightPx() {
+        assertThat(dp.cellHeightPx).isEqualTo(260)
+    }
+
+    @Test
+    fun getCellSizeX() {
+        assertThat(dp.cellSize.x).isEqualTo(259)
+    }
+
+    @Test
+    fun getCellSizeY() {
+        assertThat(dp.cellSize.y).isEqualTo(260)
+    }
+
+    @Test
+    fun cellLayoutBorderSpacePxX() {
+        assertThat(dp.cellLayoutBorderSpacePx.x).isEqualTo(50)
+    }
+
+    @Test
+    fun cellLayoutBorderSpacePxY() {
+        assertThat(dp.cellLayoutBorderSpacePx.y).isEqualTo(50)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxLeft() {
+        assertThat(dp.cellLayoutPaddingPx.left).isEqualTo(25)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxTop() {
+        assertThat(dp.cellLayoutPaddingPx.top).isEqualTo(0)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxRight() {
+        assertThat(dp.cellLayoutPaddingPx.right).isEqualTo(25)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxBottom() {
+        assertThat(dp.cellLayoutPaddingPx.bottom).isEqualTo(25)
+    }
+
+    @Test
+    fun iconSizePx() {
+        assertThat(dp.iconSizePx).isEqualTo(112)
+    }
+
+    @Test
+    fun iconTextSizePx() {
+        assertThat(dp.iconTextSizePx).isEqualTo(28)
+    }
+
+    @Test
+    fun iconDrawablePaddingPx() {
+        assertThat(dp.iconDrawablePaddingPx).isEqualTo(14)
+    }
+
+    @Test
+    fun folderCellWidthPx() {
+        assertThat(dp.folderCellWidthPx).isEqualTo(200)
+    }
+
+    @Test
+    fun folderCellHeightPx() {
+        assertThat(dp.folderCellHeightPx).isEqualTo(260)
+    }
+
+    @Test
+    fun folderChildIconSizePx() {
+        assertThat(dp.folderChildIconSizePx).isEqualTo(112)
+    }
+
+    @Test
+    fun folderChildTextSizePx() {
+        assertThat(dp.folderChildTextSizePx).isEqualTo(28)
+    }
+
+    @Test
+    fun folderChildDrawablePaddingPx() {
+        assertThat(dp.folderChildDrawablePaddingPx).isEqualTo(36)
+    }
+
+    @Test
+    fun folderCellLayoutBorderSpaceOriginalPx() {
+        assertThat(dp.folderCellLayoutBorderSpaceOriginalPx).isEqualTo(0)
+    }
+
+    @Test
+    fun folderCellLayoutBorderSpacePxX() {
+        assertThat(dp.folderCellLayoutBorderSpacePx.x).isEqualTo(0)
+    }
+
+    @Test
+    fun folderCellLayoutBorderSpacePxY() {
+        assertThat(dp.folderCellLayoutBorderSpacePx.y).isEqualTo(0)
+    }
+
+    @Test
+    fun bottomSheetTopPadding() {
+        assertThat(dp.bottomSheetTopPadding).isEqualTo(0)
+    }
+
+    @Test
+    fun allAppsShiftRange() {
+        assertThat(dp.allAppsShiftRange).isEqualTo(1600)
+    }
+
+    @Test
+    fun allAppsTopPadding() {
+        assertThat(dp.allAppsTopPadding).isEqualTo(0)
+    }
+
+    @Test
+    fun allAppsIconSizePx() {
+        assertThat(dp.allAppsIconSizePx).isEqualTo(175)
+    }
+
+    @Test
+    fun allAppsIconTextSizePx() {
+        assertThat(dp.allAppsIconTextSizePx).isEqualTo(44)
+    }
+
+    @Test
+    fun allAppsIconDrawablePaddingPx() {
+        assertThat(dp.allAppsIconDrawablePaddingPx).isEqualTo(14)
+    }
+
+    @Test
+    fun allAppsCellHeightPx() {
+        assertThat(dp.allAppsCellHeightPx).isEqualTo(310)
+    }
+
+    @Test
+    fun allAppsCellWidthPx() {
+        assertThat(dp.allAppsCellWidthPx).isEqualTo(200)
+    }
+
+    @Test
+    fun allAppsBorderSpacePxX() {
+        assertThat(dp.allAppsBorderSpacePx.x).isEqualTo(50)
+    }
+
+    @Test
+    fun allAppsBorderSpacePxY() {
+        assertThat(dp.allAppsBorderSpacePx.y).isEqualTo(50)
+    }
+
+    @Test
+    fun numShownAllAppsColumns() {
+        assertThat(dp.numShownAllAppsColumns).isEqualTo(0)
+    }
+
+    @Test
+    fun allAppsLeftRightPadding() {
+        assertThat(dp.allAppsLeftRightPadding).isEqualTo(64)
+    }
+
+    @Test
+    fun allAppsLeftRightMargin() {
+        assertThat(dp.allAppsLeftRightMargin).isEqualTo(1241)
+    }
+
+    @Test
+    fun hotseatBarSizePx() {
+        assertThat(dp.hotseatBarSizePx).isEqualTo(386)
+    }
+
+    @Test
+    fun hotseatCellHeightPx() {
+        assertThat(dp.hotseatCellHeightPx).isEqualTo(126)
+    }
+
+    @Test
+    fun hotseatBarBottomSpacePx() {
+        assertThat(dp.hotseatBarBottomSpacePx).isEqualTo(116)
+    }
+
+    @Test
+    fun hotseatBarSidePaddingStartPx() {
+        assertThat(dp.hotseatBarSidePaddingStartPx).isEqualTo(0)
+    }
+
+    @Test
+    fun hotseatBarSidePaddingEndPx() {
+        assertThat(dp.hotseatBarSidePaddingEndPx).isEqualTo(0)
+    }
+
+    @Test
+    fun hotseatQsbSpace() {
+        assertThat(dp.hotseatQsbSpace).isEqualTo(56)
+    }
+
+    @Test
+    fun hotseatQsbHeight() {
+        assertThat(dp.hotseatQsbHeight).isEqualTo(126)
+    }
+
+    @Test
+    fun springLoadedHotseatBarTopMarginPx() {
+        assertThat(dp.springLoadedHotseatBarTopMarginPx).isEqualTo(128)
+    }
+
+    @Test
+    fun numShownHotseatIcons() {
+        assertThat(dp.numShownHotseatIcons).isEqualTo(6)
+    }
+
+    @Test
+    fun hotseatBorderSpace() {
+        assertThat(dp.hotseatBorderSpace).isEqualTo(73)
+    }
+
+    @Test
+    fun isQsbInline() {
+        assertThat(dp.isQsbInline).isEqualTo(false)
+    }
+
+    @Test
+    fun qsbWidth() {
+        assertThat(dp.qsbWidth).isEqualTo(1039)
+    }
+
+    @Test
+    fun isTaskbarPresent() {
+        assertThat(dp.isTaskbarPresent).isEqualTo(true)
+    }
+
+    @Test
+    fun isTaskbarPresentInApps() {
+        assertThat(dp.isTaskbarPresentInApps).isEqualTo(false)
+    }
+
+    @Test
+    fun taskbarSize() {
+        assertThat(dp.taskbarSize).isEqualTo(120)
+    }
+
+    @Test
+    fun desiredWorkspaceHorizontalMarginPx() {
+        assertThat(dp.desiredWorkspaceHorizontalMarginPx).isEqualTo(68)
+    }
+
+    @Test
+    fun workspacePaddingLeft() {
+        assertThat(dp.workspacePadding.left).isEqualTo(43)
+    }
+
+    @Test
+    fun workspacePaddingTop() {
+        assertThat(dp.workspacePadding.top).isEqualTo(0)
+    }
+
+    @Test
+    fun workspacePaddingRight() {
+        assertThat(dp.workspacePadding.right).isEqualTo(43)
+    }
+
+    @Test
+    fun workspacePaddingBottom() {
+        assertThat(dp.workspacePadding.bottom).isEqualTo(285)
+    }
+
+    @Test
+    fun iconScale() {
+        assertThat(dp.iconScale).isEqualTo(1)
+    }
+
+    @Test
+    fun cellScaleToFit() {
+        assertThat(dp.cellScaleToFit).isEqualTo(1.5657895f)
+    }
+
+    @Test
+    fun workspaceTopPadding() {
+        assertThat(dp.workspaceTopPadding).isEqualTo(0)
+    }
+
+    @Test
+    fun workspaceBottomPadding() {
+        assertThat(dp.workspaceBottomPadding).isEqualTo(0)
+    }
+
+    @Test
+    fun overviewTaskMarginPx() {
+        assertThat(dp.overviewTaskMarginPx).isEqualTo(32)
+    }
+
+    @Test
+    fun overviewTaskMarginGridPx() {
+        assertThat(dp.overviewTaskMarginGridPx).isEqualTo(32)
+    }
+
+    @Test
+    fun overviewTaskIconSizePx() {
+        assertThat(dp.overviewTaskIconSizePx).isEqualTo(96)
+    }
+
+    @Test
+    fun overviewTaskIconDrawableSizePx() {
+        assertThat(dp.overviewTaskIconDrawableSizePx).isEqualTo(88)
+    }
+
+    @Test
+    fun overviewTaskIconDrawableSizeGridPx() {
+        assertThat(dp.overviewTaskIconDrawableSizeGridPx).isEqualTo(88)
+    }
+
+    @Test
+    fun overviewTaskThumbnailTopMarginPx() {
+        assertThat(dp.overviewTaskThumbnailTopMarginPx).isEqualTo(160)
+    }
+
+    @Test
+    fun overviewActionsTopMarginPx() {
+        assertThat(dp.overviewActionsTopMarginPx).isEqualTo(40)
+    }
+
+    @Test
+    fun overviewActionsHeight() {
+        assertThat(dp.overviewActionsHeight).isEqualTo(96)
+    }
+
+    @Test
+    fun overviewActionsButtonSpacing() {
+        assertThat(dp.overviewActionsButtonSpacing).isEqualTo(72)
+    }
+
+    @Test
+    fun overviewPageSpacing() {
+        assertThat(dp.overviewPageSpacing).isEqualTo(88)
+    }
+
+    @Test
+    fun overviewRowSpacing() {
+        assertThat(dp.overviewRowSpacing).isEqualTo(40)
+    }
+
+    @Test
+    fun overviewGridSideMargin() {
+        assertThat(dp.overviewGridSideMargin).isEqualTo(128)
+    }
+
+    @Test
+    fun dropTargetBarTopMarginPx() {
+        assertThat(dp.dropTargetBarTopMarginPx).isEqualTo(0)
+    }
+
+    @Test
+    fun dropTargetBarSizePx() {
+        assertThat(dp.dropTargetBarSizePx).isEqualTo(144)
+    }
+
+    @Test
+    fun dropTargetBarBottomMarginPx() {
+        assertThat(dp.dropTargetBarBottomMarginPx).isEqualTo(64)
+    }
+
+    @Test
+    fun workspaceSpringLoadedMinNextPageVisiblePx() {
+        assertThat(dp.workspaceSpringLoadedMinNextPageVisiblePx).isEqualTo(48)
+    }
+
+    @Test
+    fun getWorkspaceSpringLoadScale() {
+        assertThat(dp.workspaceSpringLoadScale).isEqualTo(0.7226337f)
+    }
+
+    @Test
+    fun getCellLayoutHeight() {
+        assertThat(dp.cellLayoutHeight).isEqualTo(1215)
+    }
+
+    @Test
+    fun getCellLayoutWidth() {
+        assertThat(dp.cellLayoutWidth).isEqualTo(1237)
+    }
+
+    @Test
+    fun getPanelCount() {
+        assertThat(dp.panelCount).isEqualTo(2)
+    }
+
+    @Test
+    fun isVerticalBarLayout() {
+        assertThat(dp.isVerticalBarLayout).isEqualTo(false)
+    }
+
+    @Test
+    fun getCellLayoutSpringLoadShrunkTop() {
+        assertThat(dp.cellLayoutSpringLoadShrunkTop).isEqualTo(208)
+    }
+
+    @Test
+    fun getCellLayoutSpringLoadShrunkBottom() {
+        assertThat(dp.cellLayoutSpringLoadShrunkBottom).isEqualTo(1086)
+    }
+
+    @Test
+    fun getQsbOffsetY() {
+        assertThat(dp.qsbOffsetY).isEqualTo(272)
+    }
+
+    @Test
+    fun getTaskbarOffsetY() {
+        assertThat(dp.taskbarOffsetY).isEqualTo(112)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingLeft() {
+        assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(761)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingTop() {
+        assertThat(dp.getHotseatLayoutPadding(context).top).isEqualTo(151)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingRight() {
+        assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(761)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingBottom() {
+        assertThat(dp.getHotseatLayoutPadding(context).bottom).isEqualTo(109)
+    }
+
+    @Test
+    fun hotseatBarEndOffset() {
+        assertThat(dp.hotseatBarEndOffset).isEqualTo(0)
+    }
+
+    @Test
+    fun overviewGridRectLeft() {
+        assertThat(dp.overviewGridRect.left).isEqualTo(128)
+    }
+
+    @Test
+    fun overviewGridRectTop() {
+        assertThat(dp.overviewGridRect.top).isEqualTo(160)
+    }
+
+    @Test
+    fun overviewGridRectRight() {
+        assertThat(dp.overviewGridRect.right).isEqualTo(2432)
+    }
+
+    @Test
+    fun overviewGridRectBottom() {
+        assertThat(dp.overviewGridRect.bottom).isEqualTo(1416)
+    }
+
+    @Test
+    fun taskDimensionX() {
+        assertThat(dp.taskDimension.x).isEqualTo(2560)
+    }
+
+    @Test
+    fun taskDimensionY() {
+        assertThat(dp.taskDimension.y).isEqualTo(1480)
+    }
+
+    @Test
+    fun overviewTaskRectLeft() {
+        assertThat(dp.overviewTaskRect.left).isEqualTo(384)
+    }
+
+    @Test
+    fun overviewTaskRectTop() {
+        assertThat(dp.overviewTaskRect.top).isEqualTo(270)
+    }
+
+    @Test
+    fun overviewTaskRectRight() {
+        assertThat(dp.overviewTaskRect.right).isEqualTo(2176)
+    }
+
+    @Test
+    fun overviewTaskRectBottom() {
+        assertThat(dp.overviewTaskRect.bottom).isEqualTo(1306)
+    }
+
+    @Test
+    fun overviewGridTaskDimensionX() {
+        assertThat(dp.overviewGridTaskDimension.x).isEqualTo(723)
+    }
+
+    @Test
+    fun overviewGridTaskDimensionY() {
+        assertThat(dp.overviewGridTaskDimension.y).isEqualTo(418)
+    }
+
+    @Test
+    fun overviewModalTaskRectLeft() {
+        assertThat(dp.overviewModalTaskRect.left).isEqualTo(294)
+    }
+
+    @Test
+    fun overviewModalTaskRectTop() {
+        assertThat(dp.overviewModalTaskRect.top).isEqualTo(166)
+    }
+
+    @Test
+    fun overviewModalTaskRectRight() {
+        assertThat(dp.overviewModalTaskRect.right).isEqualTo(2266)
+    }
+
+    @Test
+    fun overviewModalTaskRectBottom() {
+        assertThat(dp.overviewModalTaskRect.bottom).isEqualTo(1306)
+    }
+
+    @Test
+    fun getGridTaskRectLeft() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).left).isEqualTo(1453)
+    }
+
+    @Test
+    fun getGridTaskRectTop() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).top).isEqualTo(270)
+    }
+
+    @Test
+    fun getGridTaskRectRight() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).right).isEqualTo(2176)
+    }
+
+    @Test
+    fun getGridTaskRectBottom() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).bottom).isEqualTo(688)
+    }
+
+    @Test
+    fun overviewTaskScale() {
+        assertThat(dp.overviewTaskWorkspaceScale).isEqualTo(0.8526749f)
+    }
+
+    @Test
+    fun overviewModalTaskScale() {
+        assertThat(dp.overviewModalTaskScale).isEqualTo(1.1003861f)
+    }
+}
\ No newline at end of file
diff --git a/quickstep/tests/src/com/android/quickstep/DeviceProfileTwoPanelPortrait3ButtonTest.kt b/quickstep/tests/src/com/android/quickstep/DeviceProfileTwoPanelPortrait3ButtonTest.kt
new file mode 100644
index 0000000..9b6d7d5
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/DeviceProfileTwoPanelPortrait3ButtonTest.kt
@@ -0,0 +1,596 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.DeviceProfileBaseTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for DeviceProfile for two panel in portrait with 3-Button navigation.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceProfileTwoPanelPortrait3ButtonTest : DeviceProfileBaseTest() {
+
+    lateinit var dp: DeviceProfile
+
+    @Before
+    fun before() {
+        initializeVarsForTablet(isTwoPanel = true, isGestureMode = false)
+        dp = newDP()
+    }
+
+    @Test
+    fun isScalableGrid() {
+        assertThat(dp.isScalableGrid).isTrue()
+    }
+
+    @Test
+    fun cellWidthPx() {
+        assertThat(dp.cellWidthPx).isEqualTo(153)
+    }
+
+    @Test
+    fun cellHeightPx() {
+        assertThat(dp.cellHeightPx).isEqualTo(199)
+    }
+
+    @Test
+    fun getCellSizeX() {
+        assertThat(dp.cellSize.x).isEqualTo(153)
+    }
+
+    @Test
+    fun getCellSizeY() {
+        assertThat(dp.cellSize.y).isEqualTo(509)
+    }
+
+    @Test
+    fun cellLayoutBorderSpacePxX() {
+        assertThat(dp.cellLayoutBorderSpacePx.x).isEqualTo(38)
+    }
+
+    @Test
+    fun cellLayoutBorderSpacePxY() {
+        assertThat(dp.cellLayoutBorderSpacePx.y).isEqualTo(38)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxLeft() {
+        assertThat(dp.cellLayoutPaddingPx.left).isEqualTo(19)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxTop() {
+        assertThat(dp.cellLayoutPaddingPx.top).isEqualTo(0)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxRight() {
+        assertThat(dp.cellLayoutPaddingPx.right).isEqualTo(19)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxBottom() {
+        assertThat(dp.cellLayoutPaddingPx.bottom).isEqualTo(19)
+    }
+
+    @Test
+    fun iconSizePx() {
+        assertThat(dp.iconSizePx).isEqualTo(112)
+    }
+
+    @Test
+    fun iconTextSizePx() {
+        assertThat(dp.iconTextSizePx).isEqualTo(28)
+    }
+
+    @Test
+    fun iconDrawablePaddingPx() {
+        assertThat(dp.iconDrawablePaddingPx).isEqualTo(14)
+    }
+
+    @Test
+    fun folderCellWidthPx() {
+        assertThat(dp.folderCellWidthPx).isEqualTo(153)
+    }
+
+    @Test
+    fun folderCellHeightPx() {
+        assertThat(dp.folderCellHeightPx).isEqualTo(199)
+    }
+
+    @Test
+    fun folderChildIconSizePx() {
+        assertThat(dp.folderChildIconSizePx).isEqualTo(112)
+    }
+
+    @Test
+    fun folderChildTextSizePx() {
+        assertThat(dp.folderChildTextSizePx).isEqualTo(28)
+    }
+
+    @Test
+    fun folderChildDrawablePaddingPx() {
+        assertThat(dp.folderChildDrawablePaddingPx).isEqualTo(16)
+    }
+
+    @Test
+    fun folderCellLayoutBorderSpaceOriginalPx() {
+        assertThat(dp.folderCellLayoutBorderSpaceOriginalPx).isEqualTo(0)
+    }
+
+    @Test
+    fun folderCellLayoutBorderSpacePxX() {
+        assertThat(dp.folderCellLayoutBorderSpacePx.x).isEqualTo(0)
+    }
+
+    @Test
+    fun folderCellLayoutBorderSpacePxY() {
+        assertThat(dp.folderCellLayoutBorderSpacePx.y).isEqualTo(0)
+    }
+
+    @Test
+    fun bottomSheetTopPadding() {
+        assertThat(dp.bottomSheetTopPadding).isEqualTo(600)
+    }
+
+    @Test
+    fun allAppsShiftRange() {
+        assertThat(dp.allAppsShiftRange).isEqualTo(1960)
+    }
+
+    @Test
+    fun allAppsTopPadding() {
+        assertThat(dp.allAppsTopPadding).isEqualTo(600)
+    }
+
+    @Test
+    fun allAppsIconSizePx() {
+        assertThat(dp.allAppsIconSizePx).isEqualTo(134)
+    }
+
+    @Test
+    fun allAppsIconTextSizePx() {
+        assertThat(dp.allAppsIconTextSizePx).isEqualTo(34)
+    }
+
+    @Test
+    fun allAppsIconDrawablePaddingPx() {
+        assertThat(dp.allAppsIconDrawablePaddingPx).isEqualTo(14)
+    }
+
+    @Test
+    fun allAppsCellHeightPx() {
+        assertThat(dp.allAppsCellHeightPx).isEqualTo(237)
+    }
+
+    @Test
+    fun allAppsCellWidthPx() {
+        assertThat(dp.allAppsCellWidthPx).isEqualTo(153)
+    }
+
+    @Test
+    fun allAppsBorderSpacePxX() {
+        assertThat(dp.allAppsBorderSpacePx.x).isEqualTo(38)
+    }
+
+    @Test
+    fun allAppsBorderSpacePxY() {
+        assertThat(dp.allAppsBorderSpacePx.y).isEqualTo(38)
+    }
+
+    @Test
+    fun numShownAllAppsColumns() {
+        assertThat(dp.numShownAllAppsColumns).isEqualTo(0)
+    }
+
+    @Test
+    fun allAppsLeftRightPadding() {
+        assertThat(dp.allAppsLeftRightPadding).isEqualTo(56)
+    }
+
+    @Test
+    fun allAppsLeftRightMargin() {
+        assertThat(dp.allAppsLeftRightMargin).isEqualTo(763)
+    }
+
+    @Test
+    fun hotseatBarSizePx() {
+        assertThat(dp.hotseatBarSizePx).isEqualTo(386)
+    }
+
+    @Test
+    fun hotseatCellHeightPx() {
+        assertThat(dp.hotseatCellHeightPx).isEqualTo(126)
+    }
+
+    @Test
+    fun hotseatBarBottomSpacePx() {
+        assertThat(dp.hotseatBarBottomSpacePx).isEqualTo(116)
+    }
+
+    @Test
+    fun hotseatBarSidePaddingStartPx() {
+        assertThat(dp.hotseatBarSidePaddingStartPx).isEqualTo(0)
+    }
+
+    @Test
+    fun hotseatBarSidePaddingEndPx() {
+        assertThat(dp.hotseatBarSidePaddingEndPx).isEqualTo(0)
+    }
+
+    @Test
+    fun hotseatQsbSpace() {
+        assertThat(dp.hotseatQsbSpace).isEqualTo(56)
+    }
+
+    @Test
+    fun hotseatQsbHeight() {
+        assertThat(dp.hotseatQsbHeight).isEqualTo(126)
+    }
+
+    @Test
+    fun springLoadedHotseatBarTopMarginPx() {
+        assertThat(dp.springLoadedHotseatBarTopMarginPx).isEqualTo(216)
+    }
+
+    @Test
+    fun numShownHotseatIcons() {
+        assertThat(dp.numShownHotseatIcons).isEqualTo(6)
+    }
+
+    @Test
+    fun hotseatBorderSpace() {
+        assertThat(dp.hotseatBorderSpace).isEqualTo(32)
+    }
+
+    @Test
+    fun isQsbInline() {
+        assertThat(dp.isQsbInline).isEqualTo(false)
+    }
+
+    @Test
+    fun qsbWidth() {
+        assertThat(dp.qsbWidth).isEqualTo(685)
+    }
+
+    @Test
+    fun isTaskbarPresent() {
+        assertThat(dp.isTaskbarPresent).isEqualTo(true)
+    }
+
+    @Test
+    fun isTaskbarPresentInApps() {
+        assertThat(dp.isTaskbarPresentInApps).isEqualTo(false)
+    }
+
+    @Test
+    fun taskbarSize() {
+        assertThat(dp.taskbarSize).isEqualTo(120)
+    }
+
+    @Test
+    fun desiredWorkspaceHorizontalMarginPx() {
+        assertThat(dp.desiredWorkspaceHorizontalMarginPx).isEqualTo(52)
+    }
+
+    @Test
+    fun workspacePaddingLeft() {
+        assertThat(dp.workspacePadding.left).isEqualTo(33)
+    }
+
+    @Test
+    fun workspacePaddingTop() {
+        assertThat(dp.workspacePadding.top).isEqualTo(0)
+    }
+
+    @Test
+    fun workspacePaddingRight() {
+        assertThat(dp.workspacePadding.right).isEqualTo(33)
+    }
+
+    @Test
+    fun workspacePaddingBottom() {
+        assertThat(dp.workspacePadding.bottom).isEqualTo(291)
+    }
+
+    @Test
+    fun iconScale() {
+        assertThat(dp.iconScale).isEqualTo(1)
+    }
+
+    @Test
+    fun cellScaleToFit() {
+        assertThat(dp.cellScaleToFit).isEqualTo(1.1976048f)
+    }
+
+    @Test
+    fun workspaceTopPadding() {
+        assertThat(dp.workspaceTopPadding).isEqualTo(0)
+    }
+
+    @Test
+    fun workspaceBottomPadding() {
+        assertThat(dp.workspaceBottomPadding).isEqualTo(0)
+    }
+
+    @Test
+    fun overviewTaskMarginPx() {
+        assertThat(dp.overviewTaskMarginPx).isEqualTo(32)
+    }
+
+    @Test
+    fun overviewTaskMarginGridPx() {
+        assertThat(dp.overviewTaskMarginGridPx).isEqualTo(32)
+    }
+
+    @Test
+    fun overviewTaskIconSizePx() {
+        assertThat(dp.overviewTaskIconSizePx).isEqualTo(96)
+    }
+
+    @Test
+    fun overviewTaskIconDrawableSizePx() {
+        assertThat(dp.overviewTaskIconDrawableSizePx).isEqualTo(88)
+    }
+
+    @Test
+    fun overviewTaskIconDrawableSizeGridPx() {
+        assertThat(dp.overviewTaskIconDrawableSizeGridPx).isEqualTo(88)
+    }
+
+    @Test
+    fun overviewTaskThumbnailTopMarginPx() {
+        assertThat(dp.overviewTaskThumbnailTopMarginPx).isEqualTo(160)
+    }
+
+    @Test
+    fun overviewActionsTopMarginPx() {
+        assertThat(dp.overviewActionsTopMarginPx).isEqualTo(48)
+    }
+
+    @Test
+    fun overviewActionsHeight() {
+        assertThat(dp.overviewActionsHeight).isEqualTo(96)
+    }
+
+    @Test
+    fun overviewActionsButtonSpacing() {
+        assertThat(dp.overviewActionsButtonSpacing).isEqualTo(72)
+    }
+
+    @Test
+    fun overviewPageSpacing() {
+        assertThat(dp.overviewPageSpacing).isEqualTo(88)
+    }
+
+    @Test
+    fun overviewRowSpacing() {
+        assertThat(dp.overviewRowSpacing).isEqualTo(40)
+    }
+
+    @Test
+    fun overviewGridSideMargin() {
+        assertThat(dp.overviewGridSideMargin).isEqualTo(128)
+    }
+
+    @Test
+    fun dropTargetBarTopMarginPx() {
+        assertThat(dp.dropTargetBarTopMarginPx).isEqualTo(220)
+    }
+
+    @Test
+    fun dropTargetBarSizePx() {
+        assertThat(dp.dropTargetBarSizePx).isEqualTo(144)
+    }
+
+    @Test
+    fun dropTargetBarBottomMarginPx() {
+        assertThat(dp.dropTargetBarBottomMarginPx).isEqualTo(96)
+    }
+
+    @Test
+    fun workspaceSpringLoadedMinNextPageVisiblePx() {
+        assertThat(dp.workspaceSpringLoadedMinNextPageVisiblePx).isEqualTo(48)
+    }
+
+    @Test
+    fun getWorkspaceSpringLoadScale() {
+        assertThat(dp.workspaceSpringLoadScale).isEqualTo(0.69064087f)
+    }
+
+    @Test
+    fun getCellLayoutHeight() {
+        assertThat(dp.cellLayoutHeight).isEqualTo(2169)
+    }
+
+    @Test
+    fun getCellLayoutWidth() {
+        assertThat(dp.cellLayoutWidth).isEqualTo(767)
+    }
+
+    @Test
+    fun getPanelCount() {
+        assertThat(dp.panelCount).isEqualTo(2)
+    }
+
+    @Test
+    fun isVerticalBarLayout() {
+        assertThat(dp.isVerticalBarLayout).isEqualTo(false)
+    }
+
+    @Test
+    fun getCellLayoutSpringLoadShrunkTop() {
+        assertThat(dp.cellLayoutSpringLoadShrunkTop).isEqualTo(460)
+    }
+
+    @Test
+    fun getCellLayoutSpringLoadShrunkBottom() {
+        assertThat(dp.cellLayoutSpringLoadShrunkBottom).isEqualTo(1958)
+    }
+
+    @Test
+    fun getQsbOffsetY() {
+        assertThat(dp.qsbOffsetY).isEqualTo(272)
+    }
+
+    @Test
+    fun getTaskbarOffsetY() {
+        assertThat(dp.taskbarOffsetY).isEqualTo(112)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingLeft() {
+        assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(340)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingTop() {
+        assertThat(dp.getHotseatLayoutPadding(context).top).isEqualTo(151)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingRight() {
+        assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(428)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingBottom() {
+        assertThat(dp.getHotseatLayoutPadding(context).bottom).isEqualTo(109)
+    }
+
+    @Test
+    fun hotseatBarEndOffset() {
+        assertThat(dp.hotseatBarEndOffset).isEqualTo(428)
+    }
+
+    @Test
+    fun overviewGridRectLeft() {
+        assertThat(dp.overviewGridRect.left).isEqualTo(128)
+    }
+
+    @Test
+    fun overviewGridRectTop() {
+        assertThat(dp.overviewGridRect.top).isEqualTo(160)
+    }
+
+    @Test
+    fun overviewGridRectRight() {
+        assertThat(dp.overviewGridRect.right).isEqualTo(1472)
+    }
+
+    @Test
+    fun overviewGridRectBottom() {
+        assertThat(dp.overviewGridRect.bottom).isEqualTo(2292)
+    }
+
+    @Test
+    fun taskDimensionX() {
+        assertThat(dp.taskDimension.x).isEqualTo(1600)
+    }
+
+    @Test
+    fun taskDimensionY() {
+        assertThat(dp.taskDimension.y).isEqualTo(2440)
+    }
+
+    @Test
+    fun overviewTaskRectLeft() {
+        assertThat(dp.overviewTaskRect.left).isEqualTo(240)
+    }
+
+    @Test
+    fun overviewTaskRectTop() {
+        assertThat(dp.overviewTaskRect.top).isEqualTo(372)
+    }
+
+    @Test
+    fun overviewTaskRectRight() {
+        assertThat(dp.overviewTaskRect.right).isEqualTo(1360)
+    }
+
+    @Test
+    fun overviewTaskRectBottom() {
+        assertThat(dp.overviewTaskRect.bottom).isEqualTo(2080)
+    }
+
+    @Test
+    fun overviewGridTaskDimensionX() {
+        assertThat(dp.overviewGridTaskDimension.x).isEqualTo(494)
+    }
+
+    @Test
+    fun overviewGridTaskDimensionY() {
+        assertThat(dp.overviewGridTaskDimension.y).isEqualTo(754)
+    }
+
+    @Test
+    fun overviewModalTaskRectLeft() {
+        assertThat(dp.overviewModalTaskRect.left).isEqualTo(184)
+    }
+
+    @Test
+    fun overviewModalTaskRectTop() {
+        assertThat(dp.overviewModalTaskRect.top).isEqualTo(201)
+    }
+
+    @Test
+    fun overviewModalTaskRectRight() {
+        assertThat(dp.overviewModalTaskRect.right).isEqualTo(1416)
+    }
+
+    @Test
+    fun overviewModalTaskRectBottom() {
+        assertThat(dp.overviewModalTaskRect.bottom).isEqualTo(2080)
+    }
+
+    @Test
+    fun getGridTaskRectLeft() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).left).isEqualTo(866)
+    }
+
+    @Test
+    fun getGridTaskRectTop() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).top).isEqualTo(372)
+    }
+
+    @Test
+    fun getGridTaskRectRight() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).right).isEqualTo(1360)
+    }
+
+    @Test
+    fun getGridTaskRectBottom() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).bottom).isEqualTo(1126)
+    }
+
+    @Test
+    fun overviewTaskScale() {
+        assertThat(dp.overviewTaskWorkspaceScale).isEqualTo(0.7874597f)
+    }
+
+    @Test
+    fun overviewModalTaskScale() {
+        assertThat(dp.overviewModalTaskScale).isEqualTo(1.1f)
+    }
+}
\ No newline at end of file
diff --git a/quickstep/tests/src/com/android/quickstep/DeviceProfileTwoPanelPortraitTest.kt b/quickstep/tests/src/com/android/quickstep/DeviceProfileTwoPanelPortraitTest.kt
new file mode 100644
index 0000000..6702eb5
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/DeviceProfileTwoPanelPortraitTest.kt
@@ -0,0 +1,596 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.DeviceProfileBaseTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for DeviceProfile for two panel in portrait.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceProfileTwoPanelPortraitTest : DeviceProfileBaseTest() {
+
+    lateinit var dp: DeviceProfile
+
+    @Before
+    fun before() {
+        initializeVarsForTablet(isTwoPanel = true)
+        dp = newDP()
+    }
+
+    @Test
+    fun isScalableGrid() {
+        assertThat(dp.isScalableGrid).isTrue()
+    }
+
+    @Test
+    fun cellWidthPx() {
+        assertThat(dp.cellWidthPx).isEqualTo(153)
+    }
+
+    @Test
+    fun cellHeightPx() {
+        assertThat(dp.cellHeightPx).isEqualTo(199)
+    }
+
+    @Test
+    fun getCellSizeX() {
+        assertThat(dp.cellSize.x).isEqualTo(153)
+    }
+
+    @Test
+    fun getCellSizeY() {
+        assertThat(dp.cellSize.y).isEqualTo(509)
+    }
+
+    @Test
+    fun cellLayoutBorderSpacePxX() {
+        assertThat(dp.cellLayoutBorderSpacePx.x).isEqualTo(38)
+    }
+
+    @Test
+    fun cellLayoutBorderSpacePxY() {
+        assertThat(dp.cellLayoutBorderSpacePx.y).isEqualTo(38)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxLeft() {
+        assertThat(dp.cellLayoutPaddingPx.left).isEqualTo(19)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxTop() {
+        assertThat(dp.cellLayoutPaddingPx.top).isEqualTo(0)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxRight() {
+        assertThat(dp.cellLayoutPaddingPx.right).isEqualTo(19)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxBottom() {
+        assertThat(dp.cellLayoutPaddingPx.bottom).isEqualTo(19)
+    }
+
+    @Test
+    fun iconSizePx() {
+        assertThat(dp.iconSizePx).isEqualTo(112)
+    }
+
+    @Test
+    fun iconTextSizePx() {
+        assertThat(dp.iconTextSizePx).isEqualTo(28)
+    }
+
+    @Test
+    fun iconDrawablePaddingPx() {
+        assertThat(dp.iconDrawablePaddingPx).isEqualTo(14)
+    }
+
+    @Test
+    fun folderCellWidthPx() {
+        assertThat(dp.folderCellWidthPx).isEqualTo(153)
+    }
+
+    @Test
+    fun folderCellHeightPx() {
+        assertThat(dp.folderCellHeightPx).isEqualTo(199)
+    }
+
+    @Test
+    fun folderChildIconSizePx() {
+        assertThat(dp.folderChildIconSizePx).isEqualTo(112)
+    }
+
+    @Test
+    fun folderChildTextSizePx() {
+        assertThat(dp.folderChildTextSizePx).isEqualTo(28)
+    }
+
+    @Test
+    fun folderChildDrawablePaddingPx() {
+        assertThat(dp.folderChildDrawablePaddingPx).isEqualTo(16)
+    }
+
+    @Test
+    fun folderCellLayoutBorderSpaceOriginalPx() {
+        assertThat(dp.folderCellLayoutBorderSpaceOriginalPx).isEqualTo(0)
+    }
+
+    @Test
+    fun folderCellLayoutBorderSpacePxX() {
+        assertThat(dp.folderCellLayoutBorderSpacePx.x).isEqualTo(0)
+    }
+
+    @Test
+    fun folderCellLayoutBorderSpacePxY() {
+        assertThat(dp.folderCellLayoutBorderSpacePx.y).isEqualTo(0)
+    }
+
+    @Test
+    fun bottomSheetTopPadding() {
+        assertThat(dp.bottomSheetTopPadding).isEqualTo(600)
+    }
+
+    @Test
+    fun allAppsShiftRange() {
+        assertThat(dp.allAppsShiftRange).isEqualTo(1960)
+    }
+
+    @Test
+    fun allAppsTopPadding() {
+        assertThat(dp.allAppsTopPadding).isEqualTo(600)
+    }
+
+    @Test
+    fun allAppsIconSizePx() {
+        assertThat(dp.allAppsIconSizePx).isEqualTo(134)
+    }
+
+    @Test
+    fun allAppsIconTextSizePx() {
+        assertThat(dp.allAppsIconTextSizePx).isEqualTo(34)
+    }
+
+    @Test
+    fun allAppsIconDrawablePaddingPx() {
+        assertThat(dp.allAppsIconDrawablePaddingPx).isEqualTo(14)
+    }
+
+    @Test
+    fun allAppsCellHeightPx() {
+        assertThat(dp.allAppsCellHeightPx).isEqualTo(237)
+    }
+
+    @Test
+    fun allAppsCellWidthPx() {
+        assertThat(dp.allAppsCellWidthPx).isEqualTo(153)
+    }
+
+    @Test
+    fun allAppsBorderSpacePxX() {
+        assertThat(dp.allAppsBorderSpacePx.x).isEqualTo(38)
+    }
+
+    @Test
+    fun allAppsBorderSpacePxY() {
+        assertThat(dp.allAppsBorderSpacePx.y).isEqualTo(38)
+    }
+
+    @Test
+    fun numShownAllAppsColumns() {
+        assertThat(dp.numShownAllAppsColumns).isEqualTo(0)
+    }
+
+    @Test
+    fun allAppsLeftRightPadding() {
+        assertThat(dp.allAppsLeftRightPadding).isEqualTo(56)
+    }
+
+    @Test
+    fun allAppsLeftRightMargin() {
+        assertThat(dp.allAppsLeftRightMargin).isEqualTo(763)
+    }
+
+    @Test
+    fun hotseatBarSizePx() {
+        assertThat(dp.hotseatBarSizePx).isEqualTo(386)
+    }
+
+    @Test
+    fun hotseatCellHeightPx() {
+        assertThat(dp.hotseatCellHeightPx).isEqualTo(126)
+    }
+
+    @Test
+    fun hotseatBarBottomSpacePx() {
+        assertThat(dp.hotseatBarBottomSpacePx).isEqualTo(116)
+    }
+
+    @Test
+    fun hotseatBarSidePaddingStartPx() {
+        assertThat(dp.hotseatBarSidePaddingStartPx).isEqualTo(0)
+    }
+
+    @Test
+    fun hotseatBarSidePaddingEndPx() {
+        assertThat(dp.hotseatBarSidePaddingEndPx).isEqualTo(0)
+    }
+
+    @Test
+    fun hotseatQsbSpace() {
+        assertThat(dp.hotseatQsbSpace).isEqualTo(56)
+    }
+
+    @Test
+    fun hotseatQsbHeight() {
+        assertThat(dp.hotseatQsbHeight).isEqualTo(126)
+    }
+
+    @Test
+    fun springLoadedHotseatBarTopMarginPx() {
+        assertThat(dp.springLoadedHotseatBarTopMarginPx).isEqualTo(216)
+    }
+
+    @Test
+    fun numShownHotseatIcons() {
+        assertThat(dp.numShownHotseatIcons).isEqualTo(6)
+    }
+
+    @Test
+    fun hotseatBorderSpace() {
+        assertThat(dp.hotseatBorderSpace).isEqualTo(2)
+    }
+
+    @Test
+    fun isQsbInline() {
+        assertThat(dp.isQsbInline).isEqualTo(false)
+    }
+
+    @Test
+    fun qsbWidth() {
+        assertThat(dp.qsbWidth).isEqualTo(685)
+    }
+
+    @Test
+    fun isTaskbarPresent() {
+        assertThat(dp.isTaskbarPresent).isEqualTo(true)
+    }
+
+    @Test
+    fun isTaskbarPresentInApps() {
+        assertThat(dp.isTaskbarPresentInApps).isEqualTo(false)
+    }
+
+    @Test
+    fun taskbarSize() {
+        assertThat(dp.taskbarSize).isEqualTo(120)
+    }
+
+    @Test
+    fun desiredWorkspaceHorizontalMarginPx() {
+        assertThat(dp.desiredWorkspaceHorizontalMarginPx).isEqualTo(52)
+    }
+
+    @Test
+    fun workspacePaddingLeft() {
+        assertThat(dp.workspacePadding.left).isEqualTo(33)
+    }
+
+    @Test
+    fun workspacePaddingTop() {
+        assertThat(dp.workspacePadding.top).isEqualTo(0)
+    }
+
+    @Test
+    fun workspacePaddingRight() {
+        assertThat(dp.workspacePadding.right).isEqualTo(33)
+    }
+
+    @Test
+    fun workspacePaddingBottom() {
+        assertThat(dp.workspacePadding.bottom).isEqualTo(291)
+    }
+
+    @Test
+    fun iconScale() {
+        assertThat(dp.iconScale).isEqualTo(1)
+    }
+
+    @Test
+    fun cellScaleToFit() {
+        assertThat(dp.cellScaleToFit).isEqualTo(1.1976048f)
+    }
+
+    @Test
+    fun workspaceTopPadding() {
+        assertThat(dp.workspaceTopPadding).isEqualTo(0)
+    }
+
+    @Test
+    fun workspaceBottomPadding() {
+        assertThat(dp.workspaceBottomPadding).isEqualTo(0)
+    }
+
+    @Test
+    fun overviewTaskMarginPx() {
+        assertThat(dp.overviewTaskMarginPx).isEqualTo(32)
+    }
+
+    @Test
+    fun overviewTaskMarginGridPx() {
+        assertThat(dp.overviewTaskMarginGridPx).isEqualTo(32)
+    }
+
+    @Test
+    fun overviewTaskIconSizePx() {
+        assertThat(dp.overviewTaskIconSizePx).isEqualTo(96)
+    }
+
+    @Test
+    fun overviewTaskIconDrawableSizePx() {
+        assertThat(dp.overviewTaskIconDrawableSizePx).isEqualTo(88)
+    }
+
+    @Test
+    fun overviewTaskIconDrawableSizeGridPx() {
+        assertThat(dp.overviewTaskIconDrawableSizeGridPx).isEqualTo(88)
+    }
+
+    @Test
+    fun overviewTaskThumbnailTopMarginPx() {
+        assertThat(dp.overviewTaskThumbnailTopMarginPx).isEqualTo(160)
+    }
+
+    @Test
+    fun overviewActionsTopMarginPx() {
+        assertThat(dp.overviewActionsTopMarginPx).isEqualTo(48)
+    }
+
+    @Test
+    fun overviewActionsHeight() {
+        assertThat(dp.overviewActionsHeight).isEqualTo(96)
+    }
+
+    @Test
+    fun overviewActionsButtonSpacing() {
+        assertThat(dp.overviewActionsButtonSpacing).isEqualTo(72)
+    }
+
+    @Test
+    fun overviewPageSpacing() {
+        assertThat(dp.overviewPageSpacing).isEqualTo(88)
+    }
+
+    @Test
+    fun overviewRowSpacing() {
+        assertThat(dp.overviewRowSpacing).isEqualTo(40)
+    }
+
+    @Test
+    fun overviewGridSideMargin() {
+        assertThat(dp.overviewGridSideMargin).isEqualTo(128)
+    }
+
+    @Test
+    fun dropTargetBarTopMarginPx() {
+        assertThat(dp.dropTargetBarTopMarginPx).isEqualTo(220)
+    }
+
+    @Test
+    fun dropTargetBarSizePx() {
+        assertThat(dp.dropTargetBarSizePx).isEqualTo(144)
+    }
+
+    @Test
+    fun dropTargetBarBottomMarginPx() {
+        assertThat(dp.dropTargetBarBottomMarginPx).isEqualTo(96)
+    }
+
+    @Test
+    fun workspaceSpringLoadedMinNextPageVisiblePx() {
+        assertThat(dp.workspaceSpringLoadedMinNextPageVisiblePx).isEqualTo(48)
+    }
+
+    @Test
+    fun getWorkspaceSpringLoadScale() {
+        assertThat(dp.workspaceSpringLoadScale).isEqualTo(0.69064087f)
+    }
+
+    @Test
+    fun getCellLayoutHeight() {
+        assertThat(dp.cellLayoutHeight).isEqualTo(2169)
+    }
+
+    @Test
+    fun getCellLayoutWidth() {
+        assertThat(dp.cellLayoutWidth).isEqualTo(767)
+    }
+
+    @Test
+    fun getPanelCount() {
+        assertThat(dp.panelCount).isEqualTo(2)
+    }
+
+    @Test
+    fun isVerticalBarLayout() {
+        assertThat(dp.isVerticalBarLayout).isEqualTo(false)
+    }
+
+    @Test
+    fun getCellLayoutSpringLoadShrunkTop() {
+        assertThat(dp.cellLayoutSpringLoadShrunkTop).isEqualTo(460)
+    }
+
+    @Test
+    fun getCellLayoutSpringLoadShrunkBottom() {
+        assertThat(dp.cellLayoutSpringLoadShrunkBottom).isEqualTo(1958)
+    }
+
+    @Test
+    fun getQsbOffsetY() {
+        assertThat(dp.qsbOffsetY).isEqualTo(272)
+    }
+
+    @Test
+    fun getTaskbarOffsetY() {
+        assertThat(dp.taskbarOffsetY).isEqualTo(112)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingLeft() {
+        assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(459)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingTop() {
+        assertThat(dp.getHotseatLayoutPadding(context).top).isEqualTo(151)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingRight() {
+        assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(459)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingBottom() {
+        assertThat(dp.getHotseatLayoutPadding(context).bottom).isEqualTo(109)
+    }
+
+    @Test
+    fun hotseatBarEndOffset() {
+        assertThat(dp.hotseatBarEndOffset).isEqualTo(0)
+    }
+
+    @Test
+    fun overviewGridRectLeft() {
+        assertThat(dp.overviewGridRect.left).isEqualTo(128)
+    }
+
+    @Test
+    fun overviewGridRectTop() {
+        assertThat(dp.overviewGridRect.top).isEqualTo(160)
+    }
+
+    @Test
+    fun overviewGridRectRight() {
+        assertThat(dp.overviewGridRect.right).isEqualTo(1472)
+    }
+
+    @Test
+    fun overviewGridRectBottom() {
+        assertThat(dp.overviewGridRect.bottom).isEqualTo(2368)
+    }
+
+    @Test
+    fun taskDimensionX() {
+        assertThat(dp.taskDimension.x).isEqualTo(1600)
+    }
+
+    @Test
+    fun taskDimensionY() {
+        assertThat(dp.taskDimension.y).isEqualTo(2440)
+    }
+
+    @Test
+    fun overviewTaskRectLeft() {
+        assertThat(dp.overviewTaskRect.left).isEqualTo(240)
+    }
+
+    @Test
+    fun overviewTaskRectTop() {
+        assertThat(dp.overviewTaskRect.top).isEqualTo(410)
+    }
+
+    @Test
+    fun overviewTaskRectRight() {
+        assertThat(dp.overviewTaskRect.right).isEqualTo(1360)
+    }
+
+    @Test
+    fun overviewTaskRectBottom() {
+        assertThat(dp.overviewTaskRect.bottom).isEqualTo(2118)
+    }
+
+    @Test
+    fun overviewGridTaskDimensionX() {
+        assertThat(dp.overviewGridTaskDimension.x).isEqualTo(494)
+    }
+
+    @Test
+    fun overviewGridTaskDimensionY() {
+        assertThat(dp.overviewGridTaskDimension.y).isEqualTo(754)
+    }
+
+    @Test
+    fun overviewModalTaskRectLeft() {
+        assertThat(dp.overviewModalTaskRect.left).isEqualTo(184)
+    }
+
+    @Test
+    fun overviewModalTaskRectTop() {
+        assertThat(dp.overviewModalTaskRect.top).isEqualTo(239)
+    }
+
+    @Test
+    fun overviewModalTaskRectRight() {
+        assertThat(dp.overviewModalTaskRect.right).isEqualTo(1416)
+    }
+
+    @Test
+    fun overviewModalTaskRectBottom() {
+        assertThat(dp.overviewModalTaskRect.bottom).isEqualTo(2118)
+    }
+
+    @Test
+    fun getGridTaskRectLeft() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).left).isEqualTo(866)
+    }
+
+    @Test
+    fun getGridTaskRectTop() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).top).isEqualTo(410)
+    }
+
+    @Test
+    fun getGridTaskRectRight() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).right).isEqualTo(1360)
+    }
+
+    @Test
+    fun getGridTaskRectBottom() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).bottom).isEqualTo(1164)
+    }
+
+    @Test
+    fun overviewTaskScale() {
+        assertThat(dp.overviewTaskWorkspaceScale).isEqualTo(0.7874597f)
+    }
+
+    @Test
+    fun overviewModalTaskScale() {
+        assertThat(dp.overviewModalTaskScale).isEqualTo(1.1f)
+    }
+}
\ No newline at end of file
diff --git a/quickstep/tests/src/com/android/quickstep/DeviceProfileVerticalBar3ButtonTest.kt b/quickstep/tests/src/com/android/quickstep/DeviceProfileVerticalBar3ButtonTest.kt
new file mode 100644
index 0000000..7e0c5f3
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/DeviceProfileVerticalBar3ButtonTest.kt
@@ -0,0 +1,596 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.DeviceProfileBaseTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for DeviceProfile for landscape phone with vertical bar and 3-Button navigation.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceProfileVerticalBar3ButtonTest : DeviceProfileBaseTest() {
+
+    lateinit var dp: DeviceProfile
+
+    @Before
+    fun before() {
+        initializeVarsForPhone(isVerticalBar = true, isGestureMode = false)
+        dp = newDP()
+    }
+
+    @Test
+    fun isScalableGrid() {
+        assertThat(dp.isScalableGrid).isFalse()
+    }
+
+    @Test
+    fun cellWidthPx() {
+        assertThat(dp.cellWidthPx).isEqualTo(210)
+    }
+
+    @Test
+    fun cellHeightPx() {
+        assertThat(dp.cellHeightPx).isEqualTo(221)
+    }
+
+    @Test
+    fun getCellSizeX() {
+        assertThat(dp.cellSize.x).isEqualTo(675)
+    }
+
+    @Test
+    fun getCellSizeY() {
+        assertThat(dp.cellSize.y).isEqualTo(321)
+    }
+
+    @Test
+    fun cellLayoutBorderSpacePxX() {
+        assertThat(dp.cellLayoutBorderSpacePx.x).isEqualTo(0)
+    }
+
+    @Test
+    fun cellLayoutBorderSpacePxY() {
+        assertThat(dp.cellLayoutBorderSpacePx.y).isEqualTo(0)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxLeft() {
+        assertThat(dp.cellLayoutPaddingPx.left).isEqualTo(70)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxTop() {
+        assertThat(dp.cellLayoutPaddingPx.top).isEqualTo(0)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxRight() {
+        assertThat(dp.cellLayoutPaddingPx.right).isEqualTo(70)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxBottom() {
+        assertThat(dp.cellLayoutPaddingPx.bottom).isEqualTo(53)
+    }
+
+    @Test
+    fun iconSizePx() {
+        assertThat(dp.iconSizePx).isEqualTo(196)
+    }
+
+    @Test
+    fun iconTextSizePx() {
+        assertThat(dp.iconTextSizePx).isEqualTo(0)
+    }
+
+    @Test
+    fun iconDrawablePaddingPx() {
+        assertThat(dp.iconDrawablePaddingPx).isEqualTo(0)
+    }
+
+    @Test
+    fun folderCellWidthPx() {
+        assertThat(dp.folderCellWidthPx).isEqualTo(260)
+    }
+
+    @Test
+    fun folderCellHeightPx() {
+        assertThat(dp.folderCellHeightPx).isEqualTo(304)
+    }
+
+    @Test
+    fun folderChildIconSizePx() {
+        assertThat(dp.folderChildIconSizePx).isEqualTo(196)
+    }
+
+    @Test
+    fun folderChildTextSizePx() {
+        assertThat(dp.folderChildTextSizePx).isEqualTo(49)
+    }
+
+    @Test
+    fun folderChildDrawablePaddingPx() {
+        assertThat(dp.folderChildDrawablePaddingPx).isEqualTo(14)
+    }
+
+    @Test
+    fun folderCellLayoutBorderSpaceOriginalPx() {
+        assertThat(dp.folderCellLayoutBorderSpaceOriginalPx).isEqualTo(0)
+    }
+
+    @Test
+    fun folderCellLayoutBorderSpacePxX() {
+        assertThat(dp.folderCellLayoutBorderSpacePx.x).isEqualTo(0)
+    }
+
+    @Test
+    fun folderCellLayoutBorderSpacePxY() {
+        assertThat(dp.folderCellLayoutBorderSpacePx.y).isEqualTo(0)
+    }
+
+    @Test
+    fun bottomSheetTopPadding() {
+        assertThat(dp.bottomSheetTopPadding).isEqualTo(53)
+    }
+
+    @Test
+    fun allAppsShiftRange() {
+        assertThat(dp.allAppsShiftRange).isEqualTo(1050)
+    }
+
+    @Test
+    fun allAppsTopPadding() {
+        assertThat(dp.allAppsTopPadding).isEqualTo(0)
+    }
+
+    @Test
+    fun allAppsIconSizePx() {
+        assertThat(dp.allAppsIconSizePx).isEqualTo(196)
+    }
+
+    @Test
+    fun allAppsIconTextSizePx() {
+        assertThat(dp.allAppsIconTextSizePx).isEqualTo(49)
+    }
+
+    @Test
+    fun allAppsIconDrawablePaddingPx() {
+        assertThat(dp.allAppsIconDrawablePaddingPx).isEqualTo(28)
+    }
+
+    @Test
+    fun allAppsCellHeightPx() {
+        assertThat(dp.allAppsCellHeightPx).isEqualTo(422)
+    }
+
+    @Test
+    fun allAppsCellWidthPx() {
+        assertThat(dp.allAppsCellWidthPx).isEqualTo(252)
+    }
+
+    @Test
+    fun allAppsBorderSpacePxX() {
+        assertThat(dp.allAppsBorderSpacePx.x).isEqualTo(56)
+    }
+
+    @Test
+    fun allAppsBorderSpacePxY() {
+        assertThat(dp.allAppsBorderSpacePx.y).isEqualTo(56)
+    }
+
+    @Test
+    fun numShownAllAppsColumns() {
+        assertThat(dp.numShownAllAppsColumns).isEqualTo(0)
+    }
+
+    @Test
+    fun allAppsLeftRightPadding() {
+        assertThat(dp.allAppsLeftRightPadding).isEqualTo(0)
+    }
+
+    @Test
+    fun allAppsLeftRightMargin() {
+        assertThat(dp.allAppsLeftRightMargin).isEqualTo(0)
+    }
+
+    @Test
+    fun hotseatBarSizePx() {
+        assertThat(dp.hotseatBarSizePx).isEqualTo(336)
+    }
+
+    @Test
+    fun hotseatCellHeightPx() {
+        assertThat(dp.hotseatCellHeightPx).isEqualTo(221)
+    }
+
+    @Test
+    fun hotseatBarBottomSpacePx() {
+        assertThat(dp.hotseatBarBottomSpacePx).isEqualTo(168)
+    }
+
+    @Test
+    fun hotseatBarSidePaddingStartPx() {
+        assertThat(dp.hotseatBarSidePaddingStartPx).isEqualTo(84)
+    }
+
+    @Test
+    fun hotseatBarSidePaddingEndPx() {
+        assertThat(dp.hotseatBarSidePaddingEndPx).isEqualTo(56)
+    }
+
+    @Test
+    fun hotseatQsbSpace() {
+        assertThat(dp.hotseatQsbSpace).isEqualTo(126)
+    }
+
+    @Test
+    fun hotseatQsbHeight() {
+        assertThat(dp.hotseatQsbHeight).isEqualTo(221)
+    }
+
+    @Test
+    fun springLoadedHotseatBarTopMarginPx() {
+        assertThat(dp.springLoadedHotseatBarTopMarginPx).isEqualTo(158)
+    }
+
+    @Test
+    fun numShownHotseatIcons() {
+        assertThat(dp.numShownHotseatIcons).isEqualTo(4)
+    }
+
+    @Test
+    fun hotseatBorderSpace() {
+        assertThat(dp.hotseatBorderSpace).isEqualTo(0)
+    }
+
+    @Test
+    fun isQsbInline() {
+        assertThat(dp.isQsbInline).isEqualTo(false)
+    }
+
+    @Test
+    fun qsbWidth() {
+        assertThat(dp.qsbWidth).isEqualTo(2221)
+    }
+
+    @Test
+    fun isTaskbarPresent() {
+        assertThat(dp.isTaskbarPresent).isEqualTo(false)
+    }
+
+    @Test
+    fun isTaskbarPresentInApps() {
+        assertThat(dp.isTaskbarPresentInApps).isEqualTo(false)
+    }
+
+    @Test
+    fun taskbarSize() {
+        assertThat(dp.taskbarSize).isEqualTo(0)
+    }
+
+    @Test
+    fun desiredWorkspaceHorizontalMarginPx() {
+        assertThat(dp.desiredWorkspaceHorizontalMarginPx).isEqualTo(0)
+    }
+
+    @Test
+    fun workspacePaddingLeft() {
+        assertThat(dp.workspacePadding.left).isEqualTo(14)
+    }
+
+    @Test
+    fun workspacePaddingTop() {
+        assertThat(dp.workspacePadding.top).isEqualTo(0)
+    }
+
+    @Test
+    fun workspacePaddingRight() {
+        assertThat(dp.workspacePadding.right).isEqualTo(266)
+    }
+
+    @Test
+    fun workspacePaddingBottom() {
+        assertThat(dp.workspacePadding.bottom).isEqualTo(0)
+    }
+
+    @Test
+    fun iconScale() {
+        assertThat(dp.iconScale).isEqualTo(1)
+    }
+
+    @Test
+    fun cellScaleToFit() {
+        assertThat(dp.cellScaleToFit).isEqualTo(1.0f)
+    }
+
+    @Test
+    fun workspaceTopPadding() {
+        assertThat(dp.workspaceTopPadding).isEqualTo(0)
+    }
+
+    @Test
+    fun workspaceBottomPadding() {
+        assertThat(dp.workspaceBottomPadding).isEqualTo(0)
+    }
+
+    @Test
+    fun overviewTaskMarginPx() {
+        assertThat(dp.overviewTaskMarginPx).isEqualTo(56)
+    }
+
+    @Test
+    fun overviewTaskMarginGridPx() {
+        assertThat(dp.overviewTaskMarginGridPx).isEqualTo(0)
+    }
+
+    @Test
+    fun overviewTaskIconSizePx() {
+        assertThat(dp.overviewTaskIconSizePx).isEqualTo(168)
+    }
+
+    @Test
+    fun overviewTaskIconDrawableSizePx() {
+        assertThat(dp.overviewTaskIconDrawableSizePx).isEqualTo(154)
+    }
+
+    @Test
+    fun overviewTaskIconDrawableSizeGridPx() {
+        assertThat(dp.overviewTaskIconDrawableSizeGridPx).isEqualTo(0)
+    }
+
+    @Test
+    fun overviewTaskThumbnailTopMarginPx() {
+        assertThat(dp.overviewTaskThumbnailTopMarginPx).isEqualTo(280)
+    }
+
+    @Test
+    fun overviewActionsTopMarginPx() {
+        assertThat(dp.overviewActionsTopMarginPx).isEqualTo(42)
+    }
+
+    @Test
+    fun overviewActionsHeight() {
+        assertThat(dp.overviewActionsHeight).isEqualTo(168)
+    }
+
+    @Test
+    fun overviewActionsButtonSpacing() {
+        assertThat(dp.overviewActionsButtonSpacing).isEqualTo(126)
+    }
+
+    @Test
+    fun overviewPageSpacing() {
+        assertThat(dp.overviewPageSpacing).isEqualTo(56)
+    }
+
+    @Test
+    fun overviewRowSpacing() {
+        assertThat(dp.overviewRowSpacing).isEqualTo(-112)
+    }
+
+    @Test
+    fun overviewGridSideMargin() {
+        assertThat(dp.overviewGridSideMargin).isEqualTo(0)
+    }
+
+    @Test
+    fun dropTargetBarTopMarginPx() {
+        assertThat(dp.dropTargetBarTopMarginPx).isEqualTo(21)
+    }
+
+    @Test
+    fun dropTargetBarSizePx() {
+        assertThat(dp.dropTargetBarSizePx).isEqualTo(126)
+    }
+
+    @Test
+    fun dropTargetBarBottomMarginPx() {
+        assertThat(dp.dropTargetBarBottomMarginPx).isEqualTo(21)
+    }
+
+    @Test
+    fun workspaceSpringLoadedMinNextPageVisiblePx() {
+        assertThat(dp.workspaceSpringLoadedMinNextPageVisiblePx).isEqualTo(84)
+    }
+
+    @Test
+    fun getWorkspaceSpringLoadScale() {
+        assertThat(dp.workspaceSpringLoadScale).isEqualTo(0.8880597f)
+    }
+
+    @Test
+    fun getCellLayoutHeight() {
+        assertThat(dp.cellLayoutHeight).isEqualTo(1340)
+    }
+
+    @Test
+    fun getCellLayoutWidth() {
+        assertThat(dp.cellLayoutWidth).isEqualTo(2840)
+    }
+
+    @Test
+    fun getPanelCount() {
+        assertThat(dp.panelCount).isEqualTo(1)
+    }
+
+    @Test
+    fun isVerticalBarLayout() {
+        assertThat(dp.isVerticalBarLayout).isEqualTo(true)
+    }
+
+    @Test
+    fun getCellLayoutSpringLoadShrunkTop() {
+        assertThat(dp.cellLayoutSpringLoadShrunkTop).isEqualTo(168)
+    }
+
+    @Test
+    fun getCellLayoutSpringLoadShrunkBottom() {
+        assertThat(dp.cellLayoutSpringLoadShrunkBottom).isEqualTo(1358)
+    }
+
+    @Test
+    fun getQsbOffsetY() {
+        assertThat(dp.qsbOffsetY).isEqualTo(147)
+    }
+
+    @Test
+    fun getTaskbarOffsetY() {
+        assertThat(dp.taskbarOffsetY).isEqualTo(225)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingLeft() {
+        assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(56)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingTop() {
+        assertThat(dp.getHotseatLayoutPadding(context).top).isEqualTo(0)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingRight() {
+        assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(84)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingBottom() {
+        assertThat(dp.getHotseatLayoutPadding(context).bottom).isEqualTo(165)
+    }
+
+    @Test
+    fun hotseatBarEndOffset() {
+        assertThat(dp.hotseatBarEndOffset).isEqualTo(0)
+    }
+
+    @Test
+    fun overviewGridRectLeft() {
+        assertThat(dp.overviewGridRect.left).isEqualTo(0)
+    }
+
+    @Test
+    fun overviewGridRectTop() {
+        assertThat(dp.overviewGridRect.top).isEqualTo(280)
+    }
+
+    @Test
+    fun overviewGridRectRight() {
+        assertThat(dp.overviewGridRect.right).isEqualTo(3120)
+    }
+
+    @Test
+    fun overviewGridRectBottom() {
+        assertThat(dp.overviewGridRect.bottom).isEqualTo(1130)
+    }
+
+    @Test
+    fun taskDimensionX() {
+        assertThat(dp.taskDimension.x).isEqualTo(3120)
+    }
+
+    @Test
+    fun taskDimensionY() {
+        assertThat(dp.taskDimension.y).isEqualTo(1440)
+    }
+
+    @Test
+    fun overviewTaskRectLeft() {
+        assertThat(dp.overviewTaskRect.left).isEqualTo(747)
+    }
+
+    @Test
+    fun overviewTaskRectTop() {
+        assertThat(dp.overviewTaskRect.top).isEqualTo(280)
+    }
+
+    @Test
+    fun overviewTaskRectRight() {
+        assertThat(dp.overviewTaskRect.right).isEqualTo(2372)
+    }
+
+    @Test
+    fun overviewTaskRectBottom() {
+        assertThat(dp.overviewTaskRect.bottom).isEqualTo(1030)
+    }
+
+    @Test
+    fun overviewGridTaskDimensionX() {
+        assertThat(dp.overviewGridTaskDimension.x).isEqualTo(631)
+    }
+
+    @Test
+    fun overviewGridTaskDimensionY() {
+        assertThat(dp.overviewGridTaskDimension.y).isEqualTo(291)
+    }
+
+    @Test
+    fun overviewModalTaskRectLeft() {
+        assertThat(dp.overviewModalTaskRect.left).isEqualTo(666)
+    }
+
+    @Test
+    fun overviewModalTaskRectTop() {
+        assertThat(dp.overviewModalTaskRect.top).isEqualTo(205)
+    }
+
+    @Test
+    fun overviewModalTaskRectRight() {
+        assertThat(dp.overviewModalTaskRect.right).isEqualTo(2454)
+    }
+
+    @Test
+    fun overviewModalTaskRectBottom() {
+        assertThat(dp.overviewModalTaskRect.bottom).isEqualTo(1030)
+    }
+
+    @Test
+    fun getGridTaskRectLeft() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).left).isEqualTo(1741)
+    }
+
+    @Test
+    fun getGridTaskRectTop() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).top).isEqualTo(280)
+    }
+
+    @Test
+    fun getGridTaskRectRight() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).right).isEqualTo(2372)
+    }
+
+    @Test
+    fun getGridTaskRectBottom() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).bottom).isEqualTo(571)
+    }
+
+    @Test
+    fun overviewTaskScale() {
+        assertThat(dp.overviewTaskWorkspaceScale).isEqualTo(0.5597015f)
+    }
+
+    @Test
+    fun overviewModalTaskScale() {
+        assertThat(dp.overviewModalTaskScale).isEqualTo(1.1f)
+    }
+}
\ No newline at end of file
diff --git a/quickstep/tests/src/com/android/quickstep/DeviceProfileVerticalBarTest.kt b/quickstep/tests/src/com/android/quickstep/DeviceProfileVerticalBarTest.kt
new file mode 100644
index 0000000..9580458
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/DeviceProfileVerticalBarTest.kt
@@ -0,0 +1,596 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.DeviceProfileBaseTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for DeviceProfile for landscape phone with vertical bar.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceProfileVerticalBarTest : DeviceProfileBaseTest() {
+
+    lateinit var dp: DeviceProfile
+
+    @Before
+    fun before() {
+        initializeVarsForPhone(isVerticalBar = true)
+        dp = newDP()
+    }
+
+    @Test
+    fun isScalableGrid() {
+        assertThat(dp.isScalableGrid).isFalse()
+    }
+
+    @Test
+    fun cellWidthPx() {
+        assertThat(dp.cellWidthPx).isEqualTo(210)
+    }
+
+    @Test
+    fun cellHeightPx() {
+        assertThat(dp.cellHeightPx).isEqualTo(221)
+    }
+
+    @Test
+    fun getCellSizeX() {
+        assertThat(dp.cellSize.x).isEqualTo(675)
+    }
+
+    @Test
+    fun getCellSizeY() {
+        assertThat(dp.cellSize.y).isEqualTo(321)
+    }
+
+    @Test
+    fun cellLayoutBorderSpacePxX() {
+        assertThat(dp.cellLayoutBorderSpacePx.x).isEqualTo(0)
+    }
+
+    @Test
+    fun cellLayoutBorderSpacePxY() {
+        assertThat(dp.cellLayoutBorderSpacePx.y).isEqualTo(0)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxLeft() {
+        assertThat(dp.cellLayoutPaddingPx.left).isEqualTo(70)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxTop() {
+        assertThat(dp.cellLayoutPaddingPx.top).isEqualTo(0)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxRight() {
+        assertThat(dp.cellLayoutPaddingPx.right).isEqualTo(70)
+    }
+
+    @Test
+    fun cellLayoutPaddingPxBottom() {
+        assertThat(dp.cellLayoutPaddingPx.bottom).isEqualTo(53)
+    }
+
+    @Test
+    fun iconSizePx() {
+        assertThat(dp.iconSizePx).isEqualTo(196)
+    }
+
+    @Test
+    fun iconTextSizePx() {
+        assertThat(dp.iconTextSizePx).isEqualTo(0)
+    }
+
+    @Test
+    fun iconDrawablePaddingPx() {
+        assertThat(dp.iconDrawablePaddingPx).isEqualTo(0)
+    }
+
+    @Test
+    fun folderCellWidthPx() {
+        assertThat(dp.folderCellWidthPx).isEqualTo(260)
+    }
+
+    @Test
+    fun folderCellHeightPx() {
+        assertThat(dp.folderCellHeightPx).isEqualTo(304)
+    }
+
+    @Test
+    fun folderChildIconSizePx() {
+        assertThat(dp.folderChildIconSizePx).isEqualTo(196)
+    }
+
+    @Test
+    fun folderChildTextSizePx() {
+        assertThat(dp.folderChildTextSizePx).isEqualTo(49)
+    }
+
+    @Test
+    fun folderChildDrawablePaddingPx() {
+        assertThat(dp.folderChildDrawablePaddingPx).isEqualTo(14)
+    }
+
+    @Test
+    fun folderCellLayoutBorderSpaceOriginalPx() {
+        assertThat(dp.folderCellLayoutBorderSpaceOriginalPx).isEqualTo(0)
+    }
+
+    @Test
+    fun folderCellLayoutBorderSpacePxX() {
+        assertThat(dp.folderCellLayoutBorderSpacePx.x).isEqualTo(0)
+    }
+
+    @Test
+    fun folderCellLayoutBorderSpacePxY() {
+        assertThat(dp.folderCellLayoutBorderSpacePx.y).isEqualTo(0)
+    }
+
+    @Test
+    fun bottomSheetTopPadding() {
+        assertThat(dp.bottomSheetTopPadding).isEqualTo(53)
+    }
+
+    @Test
+    fun allAppsShiftRange() {
+        assertThat(dp.allAppsShiftRange).isEqualTo(1050)
+    }
+
+    @Test
+    fun allAppsTopPadding() {
+        assertThat(dp.allAppsTopPadding).isEqualTo(0)
+    }
+
+    @Test
+    fun allAppsIconSizePx() {
+        assertThat(dp.allAppsIconSizePx).isEqualTo(196)
+    }
+
+    @Test
+    fun allAppsIconTextSizePx() {
+        assertThat(dp.allAppsIconTextSizePx).isEqualTo(49)
+    }
+
+    @Test
+    fun allAppsIconDrawablePaddingPx() {
+        assertThat(dp.allAppsIconDrawablePaddingPx).isEqualTo(28)
+    }
+
+    @Test
+    fun allAppsCellHeightPx() {
+        assertThat(dp.allAppsCellHeightPx).isEqualTo(422)
+    }
+
+    @Test
+    fun allAppsCellWidthPx() {
+        assertThat(dp.allAppsCellWidthPx).isEqualTo(252)
+    }
+
+    @Test
+    fun allAppsBorderSpacePxX() {
+        assertThat(dp.allAppsBorderSpacePx.x).isEqualTo(56)
+    }
+
+    @Test
+    fun allAppsBorderSpacePxY() {
+        assertThat(dp.allAppsBorderSpacePx.y).isEqualTo(56)
+    }
+
+    @Test
+    fun numShownAllAppsColumns() {
+        assertThat(dp.numShownAllAppsColumns).isEqualTo(0)
+    }
+
+    @Test
+    fun allAppsLeftRightPadding() {
+        assertThat(dp.allAppsLeftRightPadding).isEqualTo(0)
+    }
+
+    @Test
+    fun allAppsLeftRightMargin() {
+        assertThat(dp.allAppsLeftRightMargin).isEqualTo(0)
+    }
+
+    @Test
+    fun hotseatBarSizePx() {
+        assertThat(dp.hotseatBarSizePx).isEqualTo(336)
+    }
+
+    @Test
+    fun hotseatCellHeightPx() {
+        assertThat(dp.hotseatCellHeightPx).isEqualTo(221)
+    }
+
+    @Test
+    fun hotseatBarBottomSpacePx() {
+        assertThat(dp.hotseatBarBottomSpacePx).isEqualTo(168)
+    }
+
+    @Test
+    fun hotseatBarSidePaddingStartPx() {
+        assertThat(dp.hotseatBarSidePaddingStartPx).isEqualTo(84)
+    }
+
+    @Test
+    fun hotseatBarSidePaddingEndPx() {
+        assertThat(dp.hotseatBarSidePaddingEndPx).isEqualTo(56)
+    }
+
+    @Test
+    fun hotseatQsbSpace() {
+        assertThat(dp.hotseatQsbSpace).isEqualTo(126)
+    }
+
+    @Test
+    fun hotseatQsbHeight() {
+        assertThat(dp.hotseatQsbHeight).isEqualTo(221)
+    }
+
+    @Test
+    fun springLoadedHotseatBarTopMarginPx() {
+        assertThat(dp.springLoadedHotseatBarTopMarginPx).isEqualTo(158)
+    }
+
+    @Test
+    fun numShownHotseatIcons() {
+        assertThat(dp.numShownHotseatIcons).isEqualTo(4)
+    }
+
+    @Test
+    fun hotseatBorderSpace() {
+        assertThat(dp.hotseatBorderSpace).isEqualTo(0)
+    }
+
+    @Test
+    fun isQsbInline() {
+        assertThat(dp.isQsbInline).isEqualTo(false)
+    }
+
+    @Test
+    fun qsbWidth() {
+        assertThat(dp.qsbWidth).isEqualTo(2221)
+    }
+
+    @Test
+    fun isTaskbarPresent() {
+        assertThat(dp.isTaskbarPresent).isEqualTo(false)
+    }
+
+    @Test
+    fun isTaskbarPresentInApps() {
+        assertThat(dp.isTaskbarPresentInApps).isEqualTo(false)
+    }
+
+    @Test
+    fun taskbarSize() {
+        assertThat(dp.taskbarSize).isEqualTo(0)
+    }
+
+    @Test
+    fun desiredWorkspaceHorizontalMarginPx() {
+        assertThat(dp.desiredWorkspaceHorizontalMarginPx).isEqualTo(0)
+    }
+
+    @Test
+    fun workspacePaddingLeft() {
+        assertThat(dp.workspacePadding.left).isEqualTo(14)
+    }
+
+    @Test
+    fun workspacePaddingTop() {
+        assertThat(dp.workspacePadding.top).isEqualTo(0)
+    }
+
+    @Test
+    fun workspacePaddingRight() {
+        assertThat(dp.workspacePadding.right).isEqualTo(266)
+    }
+
+    @Test
+    fun workspacePaddingBottom() {
+        assertThat(dp.workspacePadding.bottom).isEqualTo(0)
+    }
+
+    @Test
+    fun iconScale() {
+        assertThat(dp.iconScale).isEqualTo(1)
+    }
+
+    @Test
+    fun cellScaleToFit() {
+        assertThat(dp.cellScaleToFit).isEqualTo(1.0f)
+    }
+
+    @Test
+    fun workspaceTopPadding() {
+        assertThat(dp.workspaceTopPadding).isEqualTo(0)
+    }
+
+    @Test
+    fun workspaceBottomPadding() {
+        assertThat(dp.workspaceBottomPadding).isEqualTo(0)
+    }
+
+    @Test
+    fun overviewTaskMarginPx() {
+        assertThat(dp.overviewTaskMarginPx).isEqualTo(56)
+    }
+
+    @Test
+    fun overviewTaskMarginGridPx() {
+        assertThat(dp.overviewTaskMarginGridPx).isEqualTo(0)
+    }
+
+    @Test
+    fun overviewTaskIconSizePx() {
+        assertThat(dp.overviewTaskIconSizePx).isEqualTo(168)
+    }
+
+    @Test
+    fun overviewTaskIconDrawableSizePx() {
+        assertThat(dp.overviewTaskIconDrawableSizePx).isEqualTo(154)
+    }
+
+    @Test
+    fun overviewTaskIconDrawableSizeGridPx() {
+        assertThat(dp.overviewTaskIconDrawableSizeGridPx).isEqualTo(0)
+    }
+
+    @Test
+    fun overviewTaskThumbnailTopMarginPx() {
+        assertThat(dp.overviewTaskThumbnailTopMarginPx).isEqualTo(280)
+    }
+
+    @Test
+    fun overviewActionsTopMarginPx() {
+        assertThat(dp.overviewActionsTopMarginPx).isEqualTo(42)
+    }
+
+    @Test
+    fun overviewActionsHeight() {
+        assertThat(dp.overviewActionsHeight).isEqualTo(168)
+    }
+
+    @Test
+    fun overviewActionsButtonSpacing() {
+        assertThat(dp.overviewActionsButtonSpacing).isEqualTo(126)
+    }
+
+    @Test
+    fun overviewPageSpacing() {
+        assertThat(dp.overviewPageSpacing).isEqualTo(56)
+    }
+
+    @Test
+    fun overviewRowSpacing() {
+        assertThat(dp.overviewRowSpacing).isEqualTo(-112)
+    }
+
+    @Test
+    fun overviewGridSideMargin() {
+        assertThat(dp.overviewGridSideMargin).isEqualTo(0)
+    }
+
+    @Test
+    fun dropTargetBarTopMarginPx() {
+        assertThat(dp.dropTargetBarTopMarginPx).isEqualTo(21)
+    }
+
+    @Test
+    fun dropTargetBarSizePx() {
+        assertThat(dp.dropTargetBarSizePx).isEqualTo(126)
+    }
+
+    @Test
+    fun dropTargetBarBottomMarginPx() {
+        assertThat(dp.dropTargetBarBottomMarginPx).isEqualTo(21)
+    }
+
+    @Test
+    fun workspaceSpringLoadedMinNextPageVisiblePx() {
+        assertThat(dp.workspaceSpringLoadedMinNextPageVisiblePx).isEqualTo(84)
+    }
+
+    @Test
+    fun getWorkspaceSpringLoadScale() {
+        assertThat(dp.workspaceSpringLoadScale).isEqualTo(0.8880597f)
+    }
+
+    @Test
+    fun getCellLayoutHeight() {
+        assertThat(dp.cellLayoutHeight).isEqualTo(1340)
+    }
+
+    @Test
+    fun getCellLayoutWidth() {
+        assertThat(dp.cellLayoutWidth).isEqualTo(2840)
+    }
+
+    @Test
+    fun getPanelCount() {
+        assertThat(dp.panelCount).isEqualTo(1)
+    }
+
+    @Test
+    fun isVerticalBarLayout() {
+        assertThat(dp.isVerticalBarLayout).isEqualTo(true)
+    }
+
+    @Test
+    fun getCellLayoutSpringLoadShrunkTop() {
+        assertThat(dp.cellLayoutSpringLoadShrunkTop).isEqualTo(168)
+    }
+
+    @Test
+    fun getCellLayoutSpringLoadShrunkBottom() {
+        assertThat(dp.cellLayoutSpringLoadShrunkBottom).isEqualTo(1358)
+    }
+
+    @Test
+    fun getQsbOffsetY() {
+        assertThat(dp.qsbOffsetY).isEqualTo(147)
+    }
+
+    @Test
+    fun getTaskbarOffsetY() {
+        assertThat(dp.taskbarOffsetY).isEqualTo(225)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingLeft() {
+        assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(56)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingTop() {
+        assertThat(dp.getHotseatLayoutPadding(context).top).isEqualTo(0)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingRight() {
+        assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(84)
+    }
+
+    @Test
+    fun getHotseatLayoutPaddingBottom() {
+        assertThat(dp.getHotseatLayoutPadding(context).bottom).isEqualTo(165)
+    }
+
+    @Test
+    fun hotseatBarEndOffset() {
+        assertThat(dp.hotseatBarEndOffset).isEqualTo(0)
+    }
+
+    @Test
+    fun overviewGridRectLeft() {
+        assertThat(dp.overviewGridRect.left).isEqualTo(0)
+    }
+
+    @Test
+    fun overviewGridRectTop() {
+        assertThat(dp.overviewGridRect.top).isEqualTo(280)
+    }
+
+    @Test
+    fun overviewGridRectRight() {
+        assertThat(dp.overviewGridRect.right).isEqualTo(3120)
+    }
+
+    @Test
+    fun overviewGridRectBottom() {
+        assertThat(dp.overviewGridRect.bottom).isEqualTo(1130)
+    }
+
+    @Test
+    fun taskDimensionX() {
+        assertThat(dp.taskDimension.x).isEqualTo(3120)
+    }
+
+    @Test
+    fun taskDimensionY() {
+        assertThat(dp.taskDimension.y).isEqualTo(1440)
+    }
+
+    @Test
+    fun overviewTaskRectLeft() {
+        assertThat(dp.overviewTaskRect.left).isEqualTo(747)
+    }
+
+    @Test
+    fun overviewTaskRectTop() {
+        assertThat(dp.overviewTaskRect.top).isEqualTo(280)
+    }
+
+    @Test
+    fun overviewTaskRectRight() {
+        assertThat(dp.overviewTaskRect.right).isEqualTo(2372)
+    }
+
+    @Test
+    fun overviewTaskRectBottom() {
+        assertThat(dp.overviewTaskRect.bottom).isEqualTo(1030)
+    }
+
+    @Test
+    fun overviewGridTaskDimensionX() {
+        assertThat(dp.overviewGridTaskDimension.x).isEqualTo(631)
+    }
+
+    @Test
+    fun overviewGridTaskDimensionY() {
+        assertThat(dp.overviewGridTaskDimension.y).isEqualTo(291)
+    }
+
+    @Test
+    fun overviewModalTaskRectLeft() {
+        assertThat(dp.overviewModalTaskRect.left).isEqualTo(666)
+    }
+
+    @Test
+    fun overviewModalTaskRectTop() {
+        assertThat(dp.overviewModalTaskRect.top).isEqualTo(205)
+    }
+
+    @Test
+    fun overviewModalTaskRectRight() {
+        assertThat(dp.overviewModalTaskRect.right).isEqualTo(2454)
+    }
+
+    @Test
+    fun overviewModalTaskRectBottom() {
+        assertThat(dp.overviewModalTaskRect.bottom).isEqualTo(1030)
+    }
+
+    @Test
+    fun getGridTaskRectLeft() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).left).isEqualTo(1741)
+    }
+
+    @Test
+    fun getGridTaskRectTop() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).top).isEqualTo(280)
+    }
+
+    @Test
+    fun getGridTaskRectRight() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).right).isEqualTo(2372)
+    }
+
+    @Test
+    fun getGridTaskRectBottom() {
+        assertThat(dp.getOverviewGridTaskRect(isRecentsRtl).bottom).isEqualTo(571)
+    }
+
+    @Test
+    fun overviewTaskScale() {
+        assertThat(dp.overviewTaskWorkspaceScale).isEqualTo(0.5597015f)
+    }
+
+    @Test
+    fun overviewModalTaskScale() {
+        assertThat(dp.overviewModalTaskScale).isEqualTo(1.1f)
+    }
+}
\ No newline at end of file
diff --git a/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java b/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
index 9e5d958..1c15e1e 100644
--- a/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
+++ b/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
@@ -35,7 +35,6 @@
 import android.util.ArrayMap;
 import android.util.DisplayMetrics;
 import android.util.Size;
-import android.view.Display;
 import android.view.MotionEvent;
 import android.view.Surface;
 
@@ -290,15 +289,17 @@
     private DisplayController.Info createDisplayInfo(Size screenSize, int rotation) {
         Point displaySize = new Point(screenSize.getWidth(), screenSize.getHeight());
         RotationUtils.rotateSize(displaySize, rotation);
-        CachedDisplayInfo cdi = new CachedDisplayInfo(displaySize, rotation);
-        WindowBounds wm = new WindowBounds(
+        CachedDisplayInfo cachedDisplayInfo = new CachedDisplayInfo(displaySize, rotation);
+        WindowBounds windowBounds = new WindowBounds(
                 new Rect(0, 0, displaySize.x, displaySize.y),
                 new Rect());
         WindowManagerProxy wmProxy = mock(WindowManagerProxy.class);
-        doReturn(cdi).when(wmProxy).getDisplayInfo(any(), any());
-        doReturn(wm).when(wmProxy).getRealBounds(any(), any(), any());
+        doReturn(cachedDisplayInfo).when(wmProxy).getDisplayInfo(any());
+        doReturn(windowBounds).when(wmProxy).getRealBounds(any(), any());
+        ArrayMap<CachedDisplayInfo, WindowBounds[]> internalDisplayBounds = new ArrayMap<>();
+        doReturn(internalDisplayBounds).when(wmProxy).estimateInternalDisplayBounds(any());
         return new DisplayController.Info(
-                getApplicationContext(), mock(Display.class), wmProxy, new ArrayMap<>());
+                getApplicationContext(), wmProxy, new ArrayMap<>());
     }
 
     private float generateTouchRegionHeight(Size screenSize, int rotation) {
diff --git a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
index 6ec6269..401b967 100644
--- a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
+++ b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
@@ -16,8 +16,6 @@
 
 package com.android.quickstep;
 
-import android.content.Intent;
-
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -43,7 +41,7 @@
         // b/143488140
         mLauncher.goHome();
         // Start an activity where the gestures start.
-        startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
+        startTestActivity(2);
     }
 
     private void runTest(String... eventSequence) {
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 4bf247c..81df3c0 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -179,6 +179,20 @@
         actionsView.clickAndDismissScreenshot();
     }
 
+    @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();
     }
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..d43aafa 100644
--- a/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
+++ b/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
@@ -23,8 +23,6 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.util.ArrayMap;
-import android.util.Pair;
-import android.view.Display;
 import android.view.Surface;
 import android.view.SurfaceControl;
 
@@ -148,7 +146,7 @@
                 int rotation = mDisplaySize.x > mDisplaySize.y
                         ? Surface.ROTATION_90 : Surface.ROTATION_0;
                 CachedDisplayInfo cdi =
-                        new CachedDisplayInfo("test-display", mDisplaySize, rotation , new Rect());
+                        new CachedDisplayInfo(mDisplaySize, rotation, new Rect());
                 WindowBounds wm = new WindowBounds(
                         new Rect(0, 0, mDisplaySize.x, mDisplaySize.y),
                         mDisplayInsets);
@@ -164,15 +162,15 @@
                 }
 
                 WindowManagerProxy wmProxy = mock(WindowManagerProxy.class);
-                doReturn(cdi).when(wmProxy).getDisplayInfo(any(), any());
-                doReturn(wm).when(wmProxy).getRealBounds(any(), any(), any());
+                doReturn(cdi).when(wmProxy).getDisplayInfo(any());
+                doReturn(wm).when(wmProxy).getRealBounds(any(), any());
 
-                ArrayMap<String, Pair<CachedDisplayInfo, WindowBounds[]>> perDisplayBoundsCache =
+                ArrayMap<CachedDisplayInfo, WindowBounds[]> perDisplayBoundsCache =
                         new ArrayMap<>();
-                perDisplayBoundsCache.put(cdi.id, Pair.create(cdi.normalize(), allBounds));
+                perDisplayBoundsCache.put(cdi.normalize(), allBounds);
 
                 DisplayController.Info mockInfo = new Info(
-                        helper.sandboxContext, mock(Display.class), wmProxy, perDisplayBoundsCache);
+                        helper.sandboxContext, wmProxy, perDisplayBoundsCache);
 
                 DisplayController controller =
                         DisplayController.INSTANCE.get(helper.sandboxContext);
diff --git a/res/drawable/bg_work_apps_paused_action_button.xml b/res/drawable/bg_work_apps_paused_action_button.xml
new file mode 100644
index 0000000..74d4693
--- /dev/null
+++ b/res/drawable/bg_work_apps_paused_action_button.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="@color/accent_ripple_color">
+    <item android:id="@android:id/mask">
+        <shape android:shape="rectangle">
+            <corners android:radius="@dimen/rounded_button_radius" />
+            <solid android:color="@android:color/white" />
+        </shape>
+    </item>
+
+    <item android:id="@android:id/background">
+        <shape android:shape="rectangle">
+            <solid android:color="?android:attr/colorControlHighlight" />
+            <corners android:radius="@dimen/rounded_button_radius" />
+        </shape>
+    </item>
+</ripple>
\ No newline at end of file
diff --git a/res/drawable/work_apps_toggle_background.xml b/res/drawable/work_apps_toggle_background.xml
index a47c8fe..6ad6c82 100644
--- a/res/drawable/work_apps_toggle_background.xml
+++ b/res/drawable/work_apps_toggle_background.xml
@@ -13,16 +13,8 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_enabled="false">
-        <shape android:shape="rectangle">
-            <corners android:radius="@dimen/work_fab_radius" />
-            <solid android:color="?android:attr/colorControlHighlight" />
-            <padding
-                android:left="@dimen/work_profile_footer_padding"
-                android:right="@dimen/work_profile_footer_padding" />
-        </shape>
-    </item>
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="@color/accent_ripple_color">
     <item>
         <shape android:shape="rectangle">
             <corners android:radius="@dimen/work_fab_radius" />
@@ -32,4 +24,4 @@
                 android:right="@dimen/work_profile_footer_padding" />
         </shape>
     </item>
-</selector>
+</ripple>
diff --git a/res/layout/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/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/work_apps_paused.xml b/res/layout/work_apps_paused.xml
index 79bce70..f614d9b 100644
--- a/res/layout/work_apps_paused.xml
+++ b/res/layout/work_apps_paused.xml
@@ -48,7 +48,7 @@
         android:textColor="?attr/workProfileOverlayTextColor"
         android:text="@string/work_apps_enable_btn_text"
         android:textAlignment="center"
-        android:background="@drawable/rounded_action_button"
+        android:background="@drawable/bg_work_apps_paused_action_button"
         android:paddingStart="16dp"
         android:paddingEnd="16dp"
         android:textSize="14sp" />
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 55139b3..445bfda 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Item is verwyder"</string>
     <string name="undo" msgid="4151576204245173321">"Ontdoen"</string>
     <string name="action_move" msgid="4339390619886385032">"Skuif item"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Skuif na ry <xliff:g id="NUMBER_0">%1$s</xliff:g> kolom <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Skuif na ry <xliff:g id="NUMBER_0">%1$s</xliff:g> kolom <xliff:g id="NUMBER_1">%2$s</xliff:g> in <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Skuif na posisie <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Skuif na gunstelingposisie <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Item geskuif"</string>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index ec69acb..474cd9f 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"ንጥል ነገር ተንቀሳቅሷል"</string>
     <string name="undo" msgid="4151576204245173321">"ቀልብስ"</string>
     <string name="action_move" msgid="4339390619886385032">"ንጥልን አንቀሳቅስ"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"ወደ ረድፍ <xliff:g id="NUMBER_0">%1$s</xliff:g> ዓምድ <xliff:g id="NUMBER_1">%2$s</xliff:g> አንቀሳቅስ"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"በ<xliff:g id="STRING">%3$s</xliff:g> ውስጥ ወደ ረድፍ <xliff:g id="NUMBER_0">%1$s</xliff:g> ዓምድ <xliff:g id="NUMBER_1">%2$s</xliff:g> ይውሰዱ"</string>
     <string name="move_to_position" msgid="6750008980455459790">"ወደ አቀማመጥ <xliff:g id="NUMBER">%1$s</xliff:g> አንቀሳቅስ"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"ወደ ተወዳጆች አቀማመጥ <xliff:g id="NUMBER">%1$s</xliff:g> አንቀሳቅስ"</string>
     <string name="item_moved" msgid="4606538322571412879">"ንጥል ተንቀሳቅሷል"</string>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 0e48179..a4f7c03 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"تمّت إزالة العنصر."</string>
     <string name="undo" msgid="4151576204245173321">"تراجع"</string>
     <string name="action_move" msgid="4339390619886385032">"نقل العنصر"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"نقل إلى الصف <xliff:g id="NUMBER_0">%1$s</xliff:g> العمود <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"انتقل إلى الصف <xliff:g id="NUMBER_0">%1$s</xliff:g> العمود <xliff:g id="NUMBER_1">%2$s</xliff:g> في <xliff:g id="STRING">%3$s</xliff:g>."</string>
     <string name="move_to_position" msgid="6750008980455459790">"نقل إلى الموضع <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"نقل إلى الموضع المفضل <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"تم نقل العنصر"</string>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index 5fc8ca6..beb9619 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"বস্তুটো আঁতৰোৱা হ’ল"</string>
     <string name="undo" msgid="4151576204245173321">"আনডু কৰক"</string>
     <string name="action_move" msgid="4339390619886385032">"বস্তু স্থানান্তৰ কৰক"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"শাৰী <xliff:g id="NUMBER_0">%1$s</xliff:g> স্তম্ভ <xliff:g id="NUMBER_1">%2$s</xliff:g>লৈ স্থানান্তৰিত কৰক"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g>ত <xliff:g id="NUMBER_0">%1$s</xliff:g> নম্বৰ শাৰী <xliff:g id="NUMBER_1">%2$s</xliff:g> নম্বৰ স্তম্ভলৈ লৈ যাওক"</string>
     <string name="move_to_position" msgid="6750008980455459790">"পছন্দৰ অৱস্থান <xliff:g id="NUMBER">%1$s</xliff:g>লৈ স্থানান্তৰিত কৰক"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"পছন্দৰ অৱস্থান <xliff:g id="NUMBER">%1$s</xliff:g>লৈ স্থানান্তৰিত কৰক"</string>
     <string name="item_moved" msgid="4606538322571412879">"বস্তুটো স্থানান্তৰ কৰা হ’ল"</string>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index ad72424..93e6f08 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Element silindi"</string>
     <string name="undo" msgid="4151576204245173321">"Ləğv edin"</string>
     <string name="action_move" msgid="4339390619886385032">"Elementi köçürün"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Sıra <xliff:g id="NUMBER_0">%1$s</xliff:g> sütun <xliff:g id="NUMBER_1">%2$s</xliff:g> köçürün"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="NUMBER_0">%1$s</xliff:g> saylı sətir, <xliff:g id="NUMBER_1">%2$s</xliff:g> saylı sütuna (<xliff:g id="STRING">%3$s</xliff:g>) köçürün"</string>
     <string name="move_to_position" msgid="6750008980455459790">"<xliff:g id="NUMBER">%1$s</xliff:g> mövqeyinə köçürün"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"<xliff:g id="NUMBER">%1$s</xliff:g> sevimlilər mövqeyinə köçürün"</string>
     <string name="item_moved" msgid="4606538322571412879">"Elementin yeri dəyişildi"</string>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index d7e85d9..ded4dc9 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Stavka je uklonjena"</string>
     <string name="undo" msgid="4151576204245173321">"Opozovi"</string>
     <string name="action_move" msgid="4339390619886385032">"Premesti stavku"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Premesti u red <xliff:g id="NUMBER_0">%1$s</xliff:g> i kolonu <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Premestite u red <xliff:g id="NUMBER_0">%1$s</xliff:g> kolonu <xliff:g id="NUMBER_1">%2$s</xliff:g> na <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Premesti na <xliff:g id="NUMBER">%1$s</xliff:g>. poziciju"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Premesti na <xliff:g id="NUMBER">%1$s</xliff:g>. poziciju u omiljenim"</string>
     <string name="item_moved" msgid="4606538322571412879">"Stavka je premeštena"</string>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index a845f9a..7fa8341 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Элемент выдалены"</string>
     <string name="undo" msgid="4151576204245173321">"Адрабіць"</string>
     <string name="action_move" msgid="4339390619886385032">"Перамясціць элемент"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Перамясціць у радок <xliff:g id="NUMBER_0">%1$s</xliff:g> слупок <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Перайсці да радка <xliff:g id="NUMBER_0">%1$s</xliff:g> у слупку <xliff:g id="NUMBER_1">%2$s</xliff:g> на старонцы <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Перамясціць у пазіцыю <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Перамясціць у абранае, у пазіцыю <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Элемент перамешчаны"</string>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 0c7fcd3..62b7796 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Елементът е премахнат"</string>
     <string name="undo" msgid="4151576204245173321">"Отмяна"</string>
     <string name="action_move" msgid="4339390619886385032">"Преместване на елемента"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Преместване към ред <xliff:g id="NUMBER_0">%1$s</xliff:g>, колона <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Преместване на <xliff:g id="STRING">%3$s</xliff:g> – ред <xliff:g id="NUMBER_0">%1$s</xliff:g>, колона <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Преместване към позиция <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Преместване към позиция <xliff:g id="NUMBER">%1$s</xliff:g> в любимите"</string>
     <string name="item_moved" msgid="4606538322571412879">"Елементът е преместен"</string>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index 5c8c342..1be272b 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"আইটেম সরানো হয়েছে"</string>
     <string name="undo" msgid="4151576204245173321">"ফিরিয়ে আনুন"</string>
     <string name="action_move" msgid="4339390619886385032">"আইটেম সরান"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"সারি <xliff:g id="NUMBER_0">%1$s</xliff:g> কলাম <xliff:g id="NUMBER_1">%2$s</xliff:g> এ সরান"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g>-এ সারি <xliff:g id="NUMBER_0">%1$s</xliff:g> কলাম <xliff:g id="NUMBER_1">%2$s</xliff:g> সরান"</string>
     <string name="move_to_position" msgid="6750008980455459790">"অবস্থানে সরান <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"পছন্দসই অবস্থানে সরান <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"আইটেম সরানো হয়েছে"</string>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 173518f..639b6b3 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Stavka je uklonjena"</string>
     <string name="undo" msgid="4151576204245173321">"Poništi"</string>
     <string name="action_move" msgid="4339390619886385032">"Premjesti stavku"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Pomjeri stavku u red <xliff:g id="NUMBER_0">%1$s</xliff:g> kolonu <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Premještanje u red <xliff:g id="NUMBER_0">%1$s</xliff:g>, kolonu <xliff:g id="NUMBER_1">%2$s</xliff:g> na <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Pomjeri stavku na poziciju <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Pomjeri stavku na poziciju <xliff:g id="NUMBER">%1$s</xliff:g> među omiljenim"</string>
     <string name="item_moved" msgid="4606538322571412879">"Stavka je premještena"</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index e6a4af2..ee35fd3 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"S\'ha suprimit l\'element"</string>
     <string name="undo" msgid="4151576204245173321">"Desfés"</string>
     <string name="action_move" msgid="4339390619886385032">"Desplaça l\'element"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Desplaça l\'element a la fila <xliff:g id="NUMBER_0">%1$s</xliff:g> i la columna <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Mou a la fila <xliff:g id="NUMBER_0">%1$s</xliff:g>, columna <xliff:g id="NUMBER_1">%2$s</xliff:g> a <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Desplaça l\'element a la posició <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Desplaça l\'element a la posició de preferits <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Element desplaçat"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 8b3ef2d..1b418c6 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Položka byla odstraněna"</string>
     <string name="undo" msgid="4151576204245173321">"Zpět"</string>
     <string name="action_move" msgid="4339390619886385032">"Přesunout položku"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Přesunout na řádek <xliff:g id="NUMBER_0">%1$s</xliff:g> do sloupce <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Přesunout na řádek <xliff:g id="NUMBER_0">%1$s</xliff:g>, sloupec <xliff:g id="NUMBER_1">%2$s</xliff:g> na ploše <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Přesunout na pozici <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Přesunout do oblíbených položek na pozici <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Položka byla přesunuta"</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 8160465..5cdadcd 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Elementet er fjernet"</string>
     <string name="undo" msgid="4151576204245173321">"Fortryd"</string>
     <string name="action_move" msgid="4339390619886385032">"Flyt element"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Flyt til række <xliff:g id="NUMBER_0">%1$s</xliff:g>, kolonne <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Flyt til række <xliff:g id="NUMBER_0">%1$s</xliff:g> kolonne <xliff:g id="NUMBER_1">%2$s</xliff:g> i <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Flyt til position <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Flyt til foretrukne position <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Elementet blev flyttet"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 912e7fa..b75ef9d 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Element entfernt"</string>
     <string name="undo" msgid="4151576204245173321">"Rückgängig"</string>
     <string name="action_move" msgid="4339390619886385032">"Element verschieben"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"In Zeile <xliff:g id="NUMBER_0">%1$s</xliff:g>, Spalte <xliff:g id="NUMBER_1">%2$s</xliff:g> verschoben"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"In <xliff:g id="STRING">%3$s</xliff:g> zu Zeile <xliff:g id="NUMBER_0">%1$s</xliff:g> Spalte <xliff:g id="NUMBER_1">%2$s</xliff:g> bewegen"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Auf Position <xliff:g id="NUMBER">%1$s</xliff:g> verschoben"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Auf Favoritenposition <xliff:g id="NUMBER">%1$s</xliff:g> verschoben"</string>
     <string name="item_moved" msgid="4606538322571412879">"Element verschoben"</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 0f613de..99c9e23 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Το στοιχείο καταργήθηκε"</string>
     <string name="undo" msgid="4151576204245173321">"Αναίρεση"</string>
     <string name="action_move" msgid="4339390619886385032">"Μετακίνηση στοιχείου"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Μετακίνηση στη σειρά <xliff:g id="NUMBER_0">%1$s</xliff:g>, στήλη <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Μετακίνηση στη σειρά <xliff:g id="NUMBER_0">%1$s</xliff:g> στήλη <xliff:g id="NUMBER_1">%2$s</xliff:g> στην <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Μετακίνηση στη θέση <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Μετακίνηση στη θέση <xliff:g id="NUMBER">%1$s</xliff:g> στα αγαπημένα"</string>
     <string name="item_moved" msgid="4606538322571412879">"Το στοιχείο καταργήθηκε"</string>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index a78c7f8..97f0528 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Item removed"</string>
     <string name="undo" msgid="4151576204245173321">"Undo"</string>
     <string name="action_move" msgid="4339390619886385032">"Move item"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Move to row <xliff:g id="NUMBER_0">%1$s</xliff:g> column <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Move to row <xliff:g id="NUMBER_0">%1$s</xliff:g> column <xliff:g id="NUMBER_1">%2$s</xliff:g> in <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Move to position <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Move to favourites position <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Item moved"</string>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index a78c7f8..97f0528 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Item removed"</string>
     <string name="undo" msgid="4151576204245173321">"Undo"</string>
     <string name="action_move" msgid="4339390619886385032">"Move item"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Move to row <xliff:g id="NUMBER_0">%1$s</xliff:g> column <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Move to row <xliff:g id="NUMBER_0">%1$s</xliff:g> column <xliff:g id="NUMBER_1">%2$s</xliff:g> in <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Move to position <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Move to favourites position <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Item moved"</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index a78c7f8..97f0528 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Item removed"</string>
     <string name="undo" msgid="4151576204245173321">"Undo"</string>
     <string name="action_move" msgid="4339390619886385032">"Move item"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Move to row <xliff:g id="NUMBER_0">%1$s</xliff:g> column <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Move to row <xliff:g id="NUMBER_0">%1$s</xliff:g> column <xliff:g id="NUMBER_1">%2$s</xliff:g> in <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Move to position <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Move to favourites position <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Item moved"</string>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index a78c7f8..97f0528 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Item removed"</string>
     <string name="undo" msgid="4151576204245173321">"Undo"</string>
     <string name="action_move" msgid="4339390619886385032">"Move item"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Move to row <xliff:g id="NUMBER_0">%1$s</xliff:g> column <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Move to row <xliff:g id="NUMBER_0">%1$s</xliff:g> column <xliff:g id="NUMBER_1">%2$s</xliff:g> in <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Move to position <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Move to favourites position <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Item moved"</string>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
index d6951ad..4977beb 100644
--- a/res/values-en-rXC/strings.xml
+++ b/res/values-en-rXC/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‎‎‏‎‏‏‏‏‎‎‏‏‏‏‏‏‎‎‏‎‎‏‎‎‎‏‏‎‎‏‎‎‎‎‎‏‎‎‎‎‏‎‏‏‏‎‏‎‎‏‏‏‎‏‏‎‎‏‎‏‏‏‎Item removed‎‏‎‎‏‎"</string>
     <string name="undo" msgid="4151576204245173321">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‎‏‏‏‎‏‎‏‎‏‏‏‎‎‏‎‎‎‏‏‏‏‏‏‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‎‎‏‎‎‏‎‎‏‎Undo‎‏‎‎‏‎"</string>
     <string name="action_move" msgid="4339390619886385032">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‏‏‎‎‎‎‏‏‏‎‎‎‏‎‎‏‏‏‎‎‏‏‎‎‎‏‏‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‏‏‏‏‎‎‎‏‎‎‎‎Move item‎‏‎‎‏‎"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‎‏‏‏‎‏‎‏‎‎‏‏‎‏‎‏‏‏‎‏‏‎‎‏‎‏‎‎‏‎‎‎‎‏‎‏‎‎‎‏‏‎‎‎‎‏‏‎‎‏‎‏‏‏‏‏‎‎‏‏‎Move to row ‎‏‎‎‏‏‎<xliff:g id="NUMBER_0">%1$s</xliff:g>‎‏‎‎‏‏‏‎ column ‎‏‎‎‏‏‎<xliff:g id="NUMBER_1">%2$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‏‏‎‏‏‎‎‏‏‏‏‏‏‎‎‏‎‏‎‏‎‏‏‎‏‏‎‎‎‎‎‎‎‏‏‎‏‎‎‏‏‎‎‏‏‏‎‏‎‏‎‏‎‎‏‎Move to row ‎‏‎‎‏‏‎<xliff:g id="NUMBER_0">%1$s</xliff:g>‎‏‎‎‏‏‏‎ column ‎‏‎‎‏‏‎<xliff:g id="NUMBER_1">%2$s</xliff:g>‎‏‎‎‏‏‏‎ in ‎‏‎‎‏‏‎<xliff:g id="STRING">%3$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="move_to_position" msgid="6750008980455459790">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‏‎‏‎‏‏‎‎‏‏‎‏‏‎‎‏‎‏‏‎‎‏‏‏‏‎‎‏‎‏‏‎‏‎‏‏‏‏‏‎‏‎‎‎‏‎‏‏‏‏‎‎‏‏‏‎‎Move to position ‎‏‎‎‏‏‎<xliff:g id="NUMBER">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‏‎‏‏‏‎‏‏‏‎‎‏‏‎‎‏‎‏‎‏‏‎‎‏‏‏‎‎‏‎‎‏‎‎‏‎‎‏‏‏‎‏‏‎‏‎‎‎‎‏‎‎‎‎‎‎Move to favorites position ‎‏‎‎‏‏‎<xliff:g id="NUMBER">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="item_moved" msgid="4606538322571412879">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‏‏‎‏‏‎‎‎‏‏‎‎‏‎‏‎‏‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‎‎‏‎‏‏‎‎‎‏‏‏‏‎Item moved‎‏‎‎‏‎"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 3897974..3ed312b 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Se eliminó el elemento."</string>
     <string name="undo" msgid="4151576204245173321">"Deshacer"</string>
     <string name="action_move" msgid="4339390619886385032">"Mover elemento"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Mover a fila <xliff:g id="NUMBER_0">%1$s</xliff:g>, columna <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Mover a la fila <xliff:g id="NUMBER_0">%1$s</xliff:g>, columna <xliff:g id="NUMBER_1">%2$s</xliff:g> en <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Mover a la posición número <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Mover a la posición de favoritos número <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Elemento movido"</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 823e5a7..567ff0d 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Elemento quitado"</string>
     <string name="undo" msgid="4151576204245173321">"Deshacer"</string>
     <string name="action_move" msgid="4339390619886385032">"Mover elemento"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Mover a la fila <xliff:g id="NUMBER_0">%1$s</xliff:g>, columna <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Mover a la fila <xliff:g id="NUMBER_0">%1$s</xliff:g>, columna <xliff:g id="NUMBER_1">%2$s</xliff:g> en <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Mover a la posición número <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Mover a la posición número <xliff:g id="NUMBER">%1$s</xliff:g> de favoritos"</string>
     <string name="item_moved" msgid="4606538322571412879">"Elemento movido"</string>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index bd2190a..c34a003 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Üksus eemaldati"</string>
     <string name="undo" msgid="4151576204245173321">"Võta tagasi"</string>
     <string name="action_move" msgid="4339390619886385032">"Teisalda üksus"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Teisaldamine <xliff:g id="NUMBER_0">%1$s</xliff:g>. rea <xliff:g id="NUMBER_1">%2$s</xliff:g>. veergu"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Teisalda reale <xliff:g id="NUMBER_0">%1$s</xliff:g> veerus <xliff:g id="NUMBER_1">%2$s</xliff:g> kohas <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Teisaldamine <xliff:g id="NUMBER">%1$s</xliff:g>. positsioonile"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Teisaldamine lemmikute <xliff:g id="NUMBER">%1$s</xliff:g>. positsioonile"</string>
     <string name="item_moved" msgid="4606538322571412879">"Üksus teisaldati"</string>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index c1eaea8..55e0c3c 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Kendu da elementua"</string>
     <string name="undo" msgid="4151576204245173321">"Desegin"</string>
     <string name="action_move" msgid="4339390619886385032">"Mugitu elementua"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Eraman <xliff:g id="NUMBER_0">%1$s</xliff:g>. errenkadara, <xliff:g id="NUMBER_1">%2$s</xliff:g>. zutabera"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Joan <xliff:g id="NUMBER_0">%1$s</xliff:g>garren errenkadako <xliff:g id="NUMBER_1">%2$s</xliff:g>garren zutabera, <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Eraman <xliff:g id="NUMBER">%1$s</xliff:g>. postura"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Eraman gogokoen <xliff:g id="NUMBER">%1$s</xliff:g>. postura"</string>
     <string name="item_moved" msgid="4606538322571412879">"Elementua mugitu da"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index d23eac2..12db5bd 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"مورد حذف شد"</string>
     <string name="undo" msgid="4151576204245173321">"واگرد"</string>
     <string name="action_move" msgid="4339390619886385032">"انتقال مورد"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"انتقال به سطر <xliff:g id="NUMBER_0">%1$s</xliff:g> ستون <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"انتقال به ردیف <xliff:g id="NUMBER_0">%1$s</xliff:g> ستون <xliff:g id="NUMBER_1">%2$s</xliff:g> در <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"انتقال به موقعیت <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"انتقال به موقعیت دلخواه <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"مورد منتقل شد"</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index ccc22f0..135c452 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Kohde poistettiin"</string>
     <string name="undo" msgid="4151576204245173321">"Kumoa"</string>
     <string name="action_move" msgid="4339390619886385032">"Siirrä kohde"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Siirrä rivin <xliff:g id="NUMBER_0">%1$s</xliff:g> sarakkeeseen <xliff:g id="NUMBER_1">%2$s</xliff:g>."</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Siirrä riviin <xliff:g id="NUMBER_0">%1$s</xliff:g> sarakkeeseen <xliff:g id="NUMBER_1">%2$s</xliff:g> näytöllä <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Siirrä kohtaan <xliff:g id="NUMBER">%1$s</xliff:g>."</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Siirrä suosikkien kohtaan <xliff:g id="NUMBER">%1$s</xliff:g>."</string>
     <string name="item_moved" msgid="4606538322571412879">"Kohde on siirretty."</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index 11bcae1..446a507 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Élément retiré"</string>
     <string name="undo" msgid="4151576204245173321">"Annuler"</string>
     <string name="action_move" msgid="4339390619886385032">"Déplacer l\'élément"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Déplacer vers rangée <xliff:g id="NUMBER_0">%1$s</xliff:g> colonne <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Déplacer à la ligne <xliff:g id="NUMBER_0">%1$s</xliff:g> colonne <xliff:g id="NUMBER_1">%2$s</xliff:g> dans <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Déplacer vers la position <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Déplacer vers la position <xliff:g id="NUMBER">%1$s</xliff:g> dans les favoris"</string>
     <string name="item_moved" msgid="4606538322571412879">"Élément déplacé"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 9b30b1e..1d623b1 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Élément supprimé"</string>
     <string name="undo" msgid="4151576204245173321">"Annuler"</string>
     <string name="action_move" msgid="4339390619886385032">"Déplacer l\'élément"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Déplacer vers la ligne <xliff:g id="NUMBER_0">%1$s</xliff:g>, colonne <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Déplacer vers la ligne <xliff:g id="NUMBER_0">%1$s</xliff:g>, colonne <xliff:g id="NUMBER_1">%2$s</xliff:g> dans <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Déplacer vers la position <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Déplacer vers la position <xliff:g id="NUMBER">%1$s</xliff:g> dans les favoris"</string>
     <string name="item_moved" msgid="4606538322571412879">"Élément déplacé"</string>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index 013011a..256c4d3 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Quitouse o elemento"</string>
     <string name="undo" msgid="4151576204245173321">"Desfacer"</string>
     <string name="action_move" msgid="4339390619886385032">"Mover elemento"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Mover á fila <xliff:g id="NUMBER_0">%1$s</xliff:g> columna <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Mover á fila <xliff:g id="NUMBER_0">%1$s</xliff:g>, columna <xliff:g id="NUMBER_1">%2$s</xliff:g> de <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Mover á posición <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Mover á posición dos favoritos <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Moveuse o elemento"</string>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 2d504e3..f1e1996 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"આઇટમ કાઢી નાખી"</string>
     <string name="undo" msgid="4151576204245173321">"રદ કરો"</string>
     <string name="action_move" msgid="4339390619886385032">"આઇટમ ખસેડો"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"<xliff:g id="NUMBER_0">%1$s</xliff:g> પંક્તિ <xliff:g id="NUMBER_1">%2$s</xliff:g> કૉલમ પર ખસેડો"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g>માં પંક્તિ <xliff:g id="NUMBER_0">%1$s</xliff:g> કૉલમ <xliff:g id="NUMBER_1">%2$s</xliff:g> પર આ આઇટમને ખસેડો"</string>
     <string name="move_to_position" msgid="6750008980455459790">"<xliff:g id="NUMBER">%1$s</xliff:g> સ્થિતિ પર ખસેડો"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"મનપસંદ સ્થિતિ <xliff:g id="NUMBER">%1$s</xliff:g> પર ખસેડો"</string>
     <string name="item_moved" msgid="4606538322571412879">"આઇટમ ખસેડી"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index de6ad1c..1b05040 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"आइटम हटाया गया"</string>
     <string name="undo" msgid="4151576204245173321">"पहले जैसा करें"</string>
     <string name="action_move" msgid="4339390619886385032">"आइटम ले जाएं"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"पंक्ति <xliff:g id="NUMBER_0">%1$s</xliff:g> स्तंभ <xliff:g id="NUMBER_1">%2$s</xliff:g> पर ले जाएं"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g> में, पंक्ति <xliff:g id="NUMBER_0">%1$s</xliff:g> कॉलम <xliff:g id="NUMBER_1">%2$s</xliff:g> पर जाएं"</string>
     <string name="move_to_position" msgid="6750008980455459790">"<xliff:g id="NUMBER">%1$s</xliff:g> स्थिति पर ले जाएं"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"<xliff:g id="NUMBER">%1$s</xliff:g> की पसंदीदा स्थिति पर ले जाएं"</string>
     <string name="item_moved" msgid="4606538322571412879">"आइटम ले जाया गया"</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 0e58e53..4a0fbe8 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Stavka je uklonjena"</string>
     <string name="undo" msgid="4151576204245173321">"Poništi"</string>
     <string name="action_move" msgid="4339390619886385032">"Premještanje stavke"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Premještanje u redak <xliff:g id="NUMBER_0">%1$s</xliff:g>, stupac <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Premjesti u redak <xliff:g id="NUMBER_0">%1$s</xliff:g> stupac <xliff:g id="NUMBER_1">%2$s</xliff:g> na <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Premještanje na položaj <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Premještanje na položaj favorita <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Stavka premještena"</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 30095d7..c1e527d 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Elem eltávolítva"</string>
     <string name="undo" msgid="4151576204245173321">"Mégse"</string>
     <string name="action_move" msgid="4339390619886385032">"Elem mozgatása"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Áthelyezés ide: <xliff:g id="NUMBER_0">%1$s</xliff:g>. sor, <xliff:g id="NUMBER_1">%2$s</xliff:g>. oszlop"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Áthelyezés a(z) <xliff:g id="NUMBER_0">%1$s</xliff:g>. sorba és a(z) <xliff:g id="NUMBER_1">%2$s</xliff:g>. oszlopba itt: <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Áthelyezés a(z) <xliff:g id="NUMBER">%1$s</xliff:g>. pozícióba"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Áthelyezés a kedvencek <xliff:g id="NUMBER">%1$s</xliff:g>. pozíciójába"</string>
     <string name="item_moved" msgid="4606538322571412879">"Elem áthelyezve"</string>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index 3b9761f..f0d01f1 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Տարրը հեռացվեց"</string>
     <string name="undo" msgid="4151576204245173321">"Հետարկել"</string>
     <string name="action_move" msgid="4339390619886385032">"Տեղափոխել տարրը"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Տեղափոխել տող <xliff:g id="NUMBER_0">%1$s</xliff:g> սյունակ <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Տեղափոխել շարք <xliff:g id="NUMBER_0">%1$s</xliff:g>, սյունակ <xliff:g id="NUMBER_1">%2$s</xliff:g> (<xliff:g id="STRING">%3$s</xliff:g>)"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Տեղափոխել դիրք <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Տեղափոխել նախընտրած դիրք՝ <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Տարրը տեղափոխվեց"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 02f4860..1a97a45 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Item dihapus"</string>
     <string name="undo" msgid="4151576204245173321">"Urungkan"</string>
     <string name="action_move" msgid="4339390619886385032">"Pindahkan item"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Pindahkan ke baris <xliff:g id="NUMBER_0">%1$s</xliff:g> kolom <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Pindahkan ke baris <xliff:g id="NUMBER_0">%1$s</xliff:g> kolom <xliff:g id="NUMBER_1">%2$s</xliff:g> di <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"PIndahkan ke posisi <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Pindahkan ke posisi favorit <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Item dipindahkan"</string>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index 17cf2e4..a1959f8 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Atriði fjarlægt"</string>
     <string name="undo" msgid="4151576204245173321">"Afturkalla"</string>
     <string name="action_move" msgid="4339390619886385032">"Færa atriði"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Færa í línu <xliff:g id="NUMBER_0">%1$s</xliff:g>, dálk <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Færðu þig í línu <xliff:g id="NUMBER_0">%1$s</xliff:g>, dálk <xliff:g id="NUMBER_1">%2$s</xliff:g> í <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Færa í stöðu <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Færa í stöðu <xliff:g id="NUMBER">%1$s</xliff:g> á festisvæði"</string>
     <string name="item_moved" msgid="4606538322571412879">"Atriði fært"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index edff814..ff4fe4f 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -137,7 +137,8 @@
     <string name="item_removed" msgid="851119963877842327">"Elemento rimosso"</string>
     <string name="undo" msgid="4151576204245173321">"Annulla"</string>
     <string name="action_move" msgid="4339390619886385032">"Sposta elemento"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Sposta a riga <xliff:g id="NUMBER_0">%1$s</xliff:g>, colonna <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <!-- no translation found for move_to_empty_cell_description (5254852678218206889) -->
+    <skip />
     <string name="move_to_position" msgid="6750008980455459790">"Sposta nella posizione <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Sposta nella posizione <xliff:g id="NUMBER">%1$s</xliff:g> dei preferiti"</string>
     <string name="item_moved" msgid="4606538322571412879">"Elemento spostato"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 7de8f08..15e200f 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"הפריט הוסר"</string>
     <string name="undo" msgid="4151576204245173321">"ביטול"</string>
     <string name="action_move" msgid="4339390619886385032">"העברת הפריט"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"העברה אל שורה <xliff:g id="NUMBER_0">%1$s</xliff:g> עמודה <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"צריך לעבור לשורה <xliff:g id="NUMBER_0">%1$s</xliff:g> ולטור <xliff:g id="NUMBER_1">%2$s</xliff:g> ב-<xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"העברה אל מיקום <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"העברה אל מיקום <xliff:g id="NUMBER">%1$s</xliff:g> במועדפים"</string>
     <string name="item_moved" msgid="4606538322571412879">"הפריט הועבר"</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index d7cbb07..2fd3ab5 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"アイテムを削除しました"</string>
     <string name="undo" msgid="4151576204245173321">"元に戻す"</string>
     <string name="action_move" msgid="4339390619886385032">"アイテムを移動"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"行<xliff:g id="NUMBER_0">%1$s</xliff:g>、列<xliff:g id="NUMBER_1">%2$s</xliff:g>に移動"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g> の行 <xliff:g id="NUMBER_0">%1$s</xliff:g>、列 <xliff:g id="NUMBER_1">%2$s</xliff:g> に移動します"</string>
     <string name="move_to_position" msgid="6750008980455459790">"位置<xliff:g id="NUMBER">%1$s</xliff:g>に移動"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"お気に入りの位置<xliff:g id="NUMBER">%1$s</xliff:g>に移動"</string>
     <string name="item_moved" msgid="4606538322571412879">"アイテムを移動しました"</string>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index ba5be87..24f95dd 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"ერთეული წაიშალა"</string>
     <string name="undo" msgid="4151576204245173321">"მოქმედების გაუქმება"</string>
     <string name="action_move" msgid="4339390619886385032">"ერთეულის გადაადგილება"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"გადატანა რიგში <xliff:g id="NUMBER_0">%1$s</xliff:g> სვეტში <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"გადაიტანეთ მწკრივი #<xliff:g id="NUMBER_0">%1$s</xliff:g> სვეტი #<xliff:g id="NUMBER_1">%2$s</xliff:g> <xliff:g id="STRING">%3$s</xliff:g>-ში"</string>
     <string name="move_to_position" msgid="6750008980455459790">"გადატანა <xliff:g id="NUMBER">%1$s</xliff:g> პოზიციაზე"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"გადატანა რჩეულთა პოზიციაზე <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"ერთეული გადაადგილდა"</string>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index 83b5a66..8ed7ec5 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Элемент жойылды"</string>
     <string name="undo" msgid="4151576204245173321">"Қайтару"</string>
     <string name="action_move" msgid="4339390619886385032">"Элементті жылжыту"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"<xliff:g id="NUMBER_0">%1$s</xliff:g>-жол, <xliff:g id="NUMBER_1">%2$s</xliff:g>-бағанға жылжыту"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g> экранында <xliff:g id="NUMBER_0">%1$s</xliff:g> жолын, <xliff:g id="NUMBER_1">%2$s</xliff:g> бағанын жылжытыңыз."</string>
     <string name="move_to_position" msgid="6750008980455459790">"<xliff:g id="NUMBER">%1$s</xliff:g>-орынға жылжыту"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"<xliff:g id="NUMBER">%1$s</xliff:g> нөмірлі таңдаулы орынға жылжыту"</string>
     <string name="item_moved" msgid="4606538322571412879">"Элемент жылжытылды"</string>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index 73ec4df..306449c 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"បានដកធាតុចេញ"</string>
     <string name="undo" msgid="4151576204245173321">"ត្រឡប់វិញ"</string>
     <string name="action_move" msgid="4339390619886385032">"ផ្លាស់ទីធាតុ"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"ផ្លាស់ទីទៅជួរដេកទី <xliff:g id="NUMBER_0">%1$s</xliff:g> ជួរឈរទី <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"ផ្លាស់ទៅជួរដេក <xliff:g id="NUMBER_0">%1$s</xliff:g> ជួរឈរ <xliff:g id="NUMBER_1">%2$s</xliff:g> ក្នុង <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"ផ្លាស់ទីទៅទីតាំង <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"ផ្លាស់ទីទៅការចូលចិត្តទីតាំងទី <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"បានផ្លាស់ទីធាតុ"</string>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 1e6f39e..a9b89b5 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"ಐಟಂ ತೆಗೆದುಹಾಕಲಾಗಿದೆ"</string>
     <string name="undo" msgid="4151576204245173321">"ರದ್ದುಮಾಡಿ"</string>
     <string name="action_move" msgid="4339390619886385032">"ಐಟಂ ಸರಿಸಿ"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"<xliff:g id="NUMBER_0">%1$s</xliff:g> ಸಾಲು <xliff:g id="NUMBER_1">%2$s</xliff:g> ಕಾಲಮ್‌ಗೆ ಸರಿಸಿ"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g> ನಲ್ಲಿ ಸಾಲು <xliff:g id="NUMBER_0">%1$s</xliff:g> ಅನ್ನು ಕಾಲಮ್ <xliff:g id="NUMBER_1">%2$s</xliff:g> ಗೆ ಸರಿಸಿ"</string>
     <string name="move_to_position" msgid="6750008980455459790">"<xliff:g id="NUMBER">%1$s</xliff:g> ಸ್ಥಾನಕ್ಕೆ ಸರಿಸಿ"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"ಮೆಚ್ಚಿನ <xliff:g id="NUMBER">%1$s</xliff:g> ಸ್ಥಾನಕ್ಕೆ ಸರಿಸಿ"</string>
     <string name="item_moved" msgid="4606538322571412879">"ಐಟಂ ಸರಿಸಲಾಗಿದೆ"</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index d5ec664..0935aa1 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"항목 삭제됨"</string>
     <string name="undo" msgid="4151576204245173321">"실행취소"</string>
     <string name="action_move" msgid="4339390619886385032">"항목 이동"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"<xliff:g id="NUMBER_0">%1$s</xliff:g>행 <xliff:g id="NUMBER_1">%2$s</xliff:g>열로 이동"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g>의 <xliff:g id="NUMBER_0">%1$s</xliff:g>행 <xliff:g id="NUMBER_1">%2$s</xliff:g>열로 이동"</string>
     <string name="move_to_position" msgid="6750008980455459790">"<xliff:g id="NUMBER">%1$s</xliff:g>번 위치로 이동"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"즐겨찾는 <xliff:g id="NUMBER">%1$s</xliff:g>번 위치로 이동"</string>
     <string name="item_moved" msgid="4606538322571412879">"항목을 이동했습니다."</string>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index ea4a7d2..755e274 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Жоюлду"</string>
     <string name="undo" msgid="4151576204245173321">"Кайтаруу"</string>
     <string name="action_move" msgid="4339390619886385032">"Муну жылдыруу"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"<xliff:g id="NUMBER_0">%1$s</xliff:g> катарга <xliff:g id="NUMBER_1">%2$s</xliff:g> тилкеге жылдыруу"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g> ичиндеги <xliff:g id="NUMBER_1">%2$s</xliff:g>-тилкенин <xliff:g id="NUMBER_0">%1$s</xliff:g>-cабына жылдыруу"</string>
     <string name="move_to_position" msgid="6750008980455459790">"<xliff:g id="NUMBER">%1$s</xliff:g> орунга жылдыруу"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Тандалмаларга <xliff:g id="NUMBER">%1$s</xliff:g> жылдыруу"</string>
     <string name="item_moved" msgid="4606538322571412879">"Нерсе жылдырылды"</string>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index 7291cf2..9e75492 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"ເອົາ​ລາຍ​ການ​ອອກ​ໄປ​ແລ້ວ"</string>
     <string name="undo" msgid="4151576204245173321">"ຍົກເລີກ"</string>
     <string name="action_move" msgid="4339390619886385032">"ຍ້າຍ​ລາຍ​ການ"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"ຍ້າຍ​ໄປ​ໃສ່​ແຖວ <xliff:g id="NUMBER_0">%1$s</xliff:g> ຖັນ <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"ຍ້າຍໄປແຖວ <xliff:g id="NUMBER_0">%1$s</xliff:g> ຖັນ <xliff:g id="NUMBER_1">%2$s</xliff:g> ໃນ <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"ຍ້າຍ​ໄປ​ໃສ່​ຕຳ​ແໜ່ງ <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"ຍ້າຍ​ໄປ​ໃສ່​ຕຳ​ແໜ່ງ​ທີ່​ມັກ <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"ຍ້າຍ​ລາຍ​ການ​ແລ້ວ"</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index ddff9d4..bbe0cfa 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Elementas perkeltas"</string>
     <string name="undo" msgid="4151576204245173321">"Anuliuoti"</string>
     <string name="action_move" msgid="4339390619886385032">"Perkelti elementą"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Perkelti į <xliff:g id="NUMBER_0">%1$s</xliff:g> eilutę, <xliff:g id="NUMBER_1">%2$s</xliff:g> stulpelį"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Perkelti į <xliff:g id="NUMBER_0">%1$s</xliff:g> eilutės <xliff:g id="NUMBER_1">%2$s</xliff:g> stulpelį, esantį <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Perkelti į <xliff:g id="NUMBER">%1$s</xliff:g> poziciją"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Perkelti į <xliff:g id="NUMBER">%1$s</xliff:g> mėgstamiausių poziciją"</string>
     <string name="item_moved" msgid="4606538322571412879">"Elementas perkeltas"</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index a5b7c66..b5836ef 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Vienums noņemts"</string>
     <string name="undo" msgid="4151576204245173321">"Atsaukt"</string>
     <string name="action_move" msgid="4339390619886385032">"Pārvietot vienumu"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Pārvietot uz <xliff:g id="NUMBER_0">%1$s</xliff:g>. rindu, <xliff:g id="NUMBER_1">%2$s</xliff:g>. kolonnu"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Pārvietot uz rindu numur <xliff:g id="NUMBER_0">%1$s</xliff:g>, kolonnu numur <xliff:g id="NUMBER_1">%2$s</xliff:g> šajā ekrānā: <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Pārvietot uz <xliff:g id="NUMBER">%1$s</xliff:g>. pozīciju"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Pārvietot uz <xliff:g id="NUMBER">%1$s</xliff:g>. izlases pozīciju"</string>
     <string name="item_moved" msgid="4606538322571412879">"Vienums pārvietots"</string>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index 32c3fc0..d452eba 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Ставката е отстранета"</string>
     <string name="undo" msgid="4151576204245173321">"Врати"</string>
     <string name="action_move" msgid="4339390619886385032">"Премести ја ставката"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Премести во ред <xliff:g id="NUMBER_0">%1$s</xliff:g> колона <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Преместете во редица <xliff:g id="NUMBER_0">%1$s</xliff:g>, колона <xliff:g id="NUMBER_1">%2$s</xliff:g> во <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Премести на место <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Премести на место <xliff:g id="NUMBER">%1$s</xliff:g> во омилени"</string>
     <string name="item_moved" msgid="4606538322571412879">"Ставката е преместена"</string>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index d25f9dd..df532c0 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"ഇനം നീക്കംചെയ്‌തു"</string>
     <string name="undo" msgid="4151576204245173321">"പഴയപടിയാക്കുക"</string>
     <string name="action_move" msgid="4339390619886385032">"ഇനം നീക്കുക"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"വരി <xliff:g id="NUMBER_0">%1$s</xliff:g> നിര <xliff:g id="NUMBER_1">%2$s</xliff:g>-ലേക്ക് നീക്കുക"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g> എന്നതിലെ <xliff:g id="NUMBER_1">%2$s</xliff:g>-ാം കോളത്തിലെ <xliff:g id="NUMBER_0">%1$s</xliff:g>-ാം വരിയിലേക്ക് നീക്കുക"</string>
     <string name="move_to_position" msgid="6750008980455459790">"<xliff:g id="NUMBER">%1$s</xliff:g>-ലേക്ക് നീക്കുക"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"ഇഷ്‌ടമുള്ള <xliff:g id="NUMBER">%1$s</xliff:g> സ്ഥാനത്തേക്ക് നീക്കുക"</string>
     <string name="item_moved" msgid="4606538322571412879">"ഇനം നീക്കി"</string>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index 0723c25..c71aaff 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Зүйлийг устгалаа"</string>
     <string name="undo" msgid="4151576204245173321">"Болих"</string>
     <string name="action_move" msgid="4339390619886385032">"Зөөх"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"<xliff:g id="NUMBER_0">%1$s</xliff:g> мөр <xliff:g id="NUMBER_1">%2$s</xliff:g> баганад зөөх"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g> дахь <xliff:g id="NUMBER_0">%1$s</xliff:g>-р мөр, <xliff:g id="NUMBER_1">%2$s</xliff:g>-р багана руу зөөх"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Байршил <xliff:g id="NUMBER">%1$s</xliff:g>-д зөөх"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Дуртай байршил болох <xliff:g id="NUMBER">%1$s</xliff:g>-д зөөх"</string>
     <string name="item_moved" msgid="4606538322571412879">"Зөөвөрлөсөн зүйл"</string>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index 06c1da7..e871073 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"आयटम काढला"</string>
     <string name="undo" msgid="4151576204245173321">"पूर्ववत करा"</string>
     <string name="action_move" msgid="4339390619886385032">"आयटम हलवा"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"पंक्ति <xliff:g id="NUMBER_0">%1$s</xliff:g> स्तंभ <xliff:g id="NUMBER_1">%2$s</xliff:g> मध्ये हलवा"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g> मधील <xliff:g id="NUMBER_0">%1$s</xliff:g> पंक्ती <xliff:g id="NUMBER_1">%2$s</xliff:g> स्तंभ यावर हलवा"</string>
     <string name="move_to_position" msgid="6750008980455459790">"<xliff:g id="NUMBER">%1$s</xliff:g> स्थानावर हलवा"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"आवडत्या <xliff:g id="NUMBER">%1$s</xliff:g> स्थानावर हलवा"</string>
     <string name="item_moved" msgid="4606538322571412879">"आयटम हलविला"</string>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index d123d14..cbfb551 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Item dialih keluar"</string>
     <string name="undo" msgid="4151576204245173321">"Buat asal"</string>
     <string name="action_move" msgid="4339390619886385032">"Alihkan Item"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Alihkan ke baris <xliff:g id="NUMBER_0">%1$s</xliff:g> lajur <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Alihkan ke baris <xliff:g id="NUMBER_0">%1$s</xliff:g> lajur <xliff:g id="NUMBER_1">%2$s</xliff:g> dalam <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Alihkan ke kedudukan <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Alihkan ke kedudukan kegemaran <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Item dialihkan"</string>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index aef6169..984ec4c 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"ဖယ်ရှားပြီးပြီ"</string>
     <string name="undo" msgid="4151576204245173321">"နောက်ပြန်ရန်"</string>
     <string name="action_move" msgid="4339390619886385032">"၎င်းအား ရွှေ့ပါ"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"အတန်း <xliff:g id="NUMBER_0">%1$s</xliff:g> အတိုင် <xliff:g id="NUMBER_1">%2$s</xliff:g> သို့ ရွှေ့ပါ"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g> တွင် အတန်း <xliff:g id="NUMBER_0">%1$s</xliff:g> ကော်လံ <xliff:g id="NUMBER_1">%2$s</xliff:g> သို့ ရွှေ့နိုင်သည်"</string>
     <string name="move_to_position" msgid="6750008980455459790">"<xliff:g id="NUMBER">%1$s</xliff:g> သို့ နေရာရွှေ့ပါ"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"စိတ်ကြိုက်နေရာ <xliff:g id="NUMBER">%1$s</xliff:g> သို့ ရွှေ့ပါ"</string>
     <string name="item_moved" msgid="4606538322571412879">"၎င်းအားရွှေ့ပြီး"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index eba195c..b29ee77 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Elementet er fjernet"</string>
     <string name="undo" msgid="4151576204245173321">"Angre"</string>
     <string name="action_move" msgid="4339390619886385032">"Flytt elementet"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Flytt til rad <xliff:g id="NUMBER_0">%1$s</xliff:g>, kolonne <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Flytt til rad <xliff:g id="NUMBER_0">%1$s</xliff:g> kolonne <xliff:g id="NUMBER_1">%2$s</xliff:g> i <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Flytt til posisjon <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Flytt til posisjonen for favoritter <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Elementet er flyttet"</string>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 4965d78..fcc1ece 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"वस्तु हटाइयो"</string>
     <string name="undo" msgid="4151576204245173321">"अन्डू गर्नुहोस्"</string>
     <string name="action_move" msgid="4339390619886385032">"वस्तु सार्नुहोस्"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"पङ्क्ति <xliff:g id="NUMBER_0">%1$s</xliff:g> स्तम्भ <xliff:g id="NUMBER_1">%2$s</xliff:g> मा सार्नुहोस्"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"यो वस्तु सारेर <xliff:g id="STRING">%3$s</xliff:g> मा रहेको रो <xliff:g id="NUMBER_0">%1$s</xliff:g> कोलम <xliff:g id="NUMBER_1">%2$s</xliff:g> मा लैजानुहोस्"</string>
     <string name="move_to_position" msgid="6750008980455459790">"स्थिति <xliff:g id="NUMBER">%1$s</xliff:g> मा सार्नुहोस्"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"मन पर्ने स्थिति <xliff:g id="NUMBER">%1$s</xliff:g> मा सार्नुहोस्"</string>
     <string name="item_moved" msgid="4606538322571412879">"वस्तु सारियो"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index e19b361..bd41952 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Item verwijderd"</string>
     <string name="undo" msgid="4151576204245173321">"Ongedaan maken"</string>
     <string name="action_move" msgid="4339390619886385032">"Item verplaatsen"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Verplaatsen naar rij <xliff:g id="NUMBER_0">%1$s</xliff:g>, kolom <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Verplaatsen naar rij <xliff:g id="NUMBER_0">%1$s</xliff:g> kolom <xliff:g id="NUMBER_1">%2$s</xliff:g> in <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Verplaatsen naar positie <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Verplaatsen naar positie <xliff:g id="NUMBER">%1$s</xliff:g> voor favorieten"</string>
     <string name="item_moved" msgid="4606538322571412879">"Item verplaatst"</string>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 111b9b2..95fa000 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"ଆଇଟମକୁ କାଢ଼ି ଦିଆଯାଇଛି"</string>
     <string name="undo" msgid="4151576204245173321">"ପୂର୍ବବତ୍‍"</string>
     <string name="action_move" msgid="4339390619886385032">"ଆଇଟମ୍‌ ଘୁଞ୍ଚାନ୍ତୁ"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"ଧାଡ଼ି <xliff:g id="NUMBER_0">%1$s</xliff:g> ସ୍ତମ୍ଭ <xliff:g id="NUMBER_1">%2$s</xliff:g>କୁ ନିଅନ୍ତୁ"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g>ରେ ଧାଡି <xliff:g id="NUMBER_0">%1$s</xliff:g> ସ୍ତମ୍ଭ <xliff:g id="NUMBER_1">%2$s</xliff:g>କୁ ମୁଭ କରନ୍ତୁ"</string>
     <string name="move_to_position" msgid="6750008980455459790">"<xliff:g id="NUMBER">%1$s</xliff:g> ସ୍ଥିତିକୁ ନିଅନ୍ତୁ"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"ପସନ୍ଦର ସ୍ଥିତି <xliff:g id="NUMBER">%1$s</xliff:g>କୁ ନିଅନ୍ତୁ"</string>
     <string name="item_moved" msgid="4606538322571412879">"ଆଇଟମ୍‌ ଘୁଞ୍ଚେଇ ଦିଆଗଲା"</string>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 3df6886..01fbee0 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"ਆਈਟਮ ਹਟਾਈ ਗਈ"</string>
     <string name="undo" msgid="4151576204245173321">"ਅਣਕੀਤਾ ਕਰੋ"</string>
     <string name="action_move" msgid="4339390619886385032">"ਆਈਟਮ ਨੂੰ ਮੂਵ ਕਰੋ"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"ਕਤਾਰ <xliff:g id="NUMBER_0">%1$s</xliff:g> ਕਾਲਮ <xliff:g id="NUMBER_1">%2$s</xliff:g> ਵਿੱਚ ਮੂਵ ਕਰੋ"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g> ਵਿੱਚ ਕਤਾਰ <xliff:g id="NUMBER_0">%1$s</xliff:g> ਦੇ ਕਾਲਮ <xliff:g id="NUMBER_1">%2$s</xliff:g> \'ਤੇ ਜਾਓ"</string>
     <string name="move_to_position" msgid="6750008980455459790">"ਸਥਿਤੀ <xliff:g id="NUMBER">%1$s</xliff:g> ਵਿੱਚ ਮੂਵ ਕਰੋ"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"ਮਨਪਸੰਦ ਸਥਿਤੀ <xliff:g id="NUMBER">%1$s</xliff:g> ਵਿੱਚ ਮੂਵ ਕਰੋ"</string>
     <string name="item_moved" msgid="4606538322571412879">"ਆਈਟਮ ਮੂਵ ਕੀਤੀ ਗਈ"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 869e91b..4b46f0b 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Element został usunięty"</string>
     <string name="undo" msgid="4151576204245173321">"Cofnij"</string>
     <string name="action_move" msgid="4339390619886385032">"Przenieś element"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Przenieś do wiersza <xliff:g id="NUMBER_0">%1$s</xliff:g> w kolumnie <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Przenieś do wiersza <xliff:g id="NUMBER_0">%1$s</xliff:g> w kolumnie <xliff:g id="NUMBER_1">%2$s</xliff:g>, komórka: <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Przenieś do pozycji <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Przenieś do pozycji ulubionych: <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Element został przeniesiony"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 1512754..dbfa0cb 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Item removido"</string>
     <string name="undo" msgid="4151576204245173321">"Anular"</string>
     <string name="action_move" msgid="4339390619886385032">"Mover item"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Mover para a linha <xliff:g id="NUMBER_0">%1$s</xliff:g>, coluna <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Mova para a linha <xliff:g id="NUMBER_0">%1$s</xliff:g>, coluna <xliff:g id="NUMBER_1">%2$s</xliff:g> em <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Mover para a posição <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Mover para a posição <xliff:g id="NUMBER">%1$s</xliff:g> dos favoritos"</string>
     <string name="item_moved" msgid="4606538322571412879">"Item movido"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 2dcb98f..3a5ed5b 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Item removido"</string>
     <string name="undo" msgid="4151576204245173321">"Desfazer"</string>
     <string name="action_move" msgid="4339390619886385032">"Mover item"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Mover para a linha <xliff:g id="NUMBER_0">%1$s</xliff:g>, coluna <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Mover para a linha <xliff:g id="NUMBER_0">%1$s</xliff:g>, coluna <xliff:g id="NUMBER_1">%2$s</xliff:g> na <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Mover para a posição <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Mover para a posição <xliff:g id="NUMBER">%1$s</xliff:g> dos favoritos"</string>
     <string name="item_moved" msgid="4606538322571412879">"Item movido"</string>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 9d9fb76..1b3d2b1 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -28,75 +28,75 @@
     <string name="shortcut_not_available" msgid="2536503539825726397">"Comanda rapidă nu este disponibilă"</string>
     <string name="home_screen" msgid="5629429142036709174">"Pagina de pornire"</string>
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Ecran împărțit"</string>
-    <string name="split_screen_position_top" msgid="1504965011158689649">"Împarte în sus"</string>
-    <string name="split_screen_position_left" msgid="7537793098851830883">"Împarte în stânga"</string>
-    <string name="split_screen_position_right" msgid="1569377524925193369">"Împarte în dreapta"</string>
+    <string name="split_screen_position_top" msgid="1504965011158689649">"Împărțiți în sus"</string>
+    <string name="split_screen_position_left" msgid="7537793098851830883">"Împărțiți în stânga"</string>
+    <string name="split_screen_position_right" msgid="1569377524925193369">"Împărțiți în dreapta"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Informații despre aplicație pentru %1$s"</string>
-    <string name="long_press_widget_to_add" msgid="3587712543577675817">"Atinge și ține apăsat pentru a muta un widget."</string>
-    <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Atinge de două ori și ține apăsat pentru a muta un widget sau folosește acțiuni personalizate."</string>
+    <string name="long_press_widget_to_add" msgid="3587712543577675817">"Atingeți și țineți apăsat pentru a muta un widget."</string>
+    <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Atingeți de două ori și țineți apăsat pentru a muta un widget sau folosiți acțiuni personalizate."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d lățime și %2$d înălțime"</string>
     <string name="widget_preview_context_description" msgid="9045841361655787574">"Widgetul <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
-    <string name="add_item_request_drag_hint" msgid="8730547755622776606">"Atinge lung widgetul pentru a-l muta pe ecranul de pornire"</string>
-    <string name="add_to_home_screen" msgid="9168649446635919791">"Adaugă pe ecranul de pornire"</string>
+    <string name="add_item_request_drag_hint" msgid="8730547755622776606">"Atingeți lung widgetul pentru a-l muta pe ecranul de pornire"</string>
+    <string name="add_to_home_screen" msgid="9168649446635919791">"Adăugați pe ecranul de pornire"</string>
     <string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Widgetul <xliff:g id="WIDGET_NAME">%1$s</xliff:g> a fost adăugat pe ecranul de pornire"</string>
     <string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# widget}few{# widgeturi}other{# de widgeturi}}"</string>
     <string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# comandă rapidă}few{# comenzi rapide}other{# de comenzi rapide}}"</string>
     <string name="widgets_and_shortcuts_count" msgid="7209136747878365116">"<xliff:g id="WIDGETS_COUNT">%1$s</xliff:g> <xliff:g id="SHORTCUTS_COUNT">%2$s</xliff:g>"</string>
     <string name="widget_button_text" msgid="2880537293434387943">"Widgeturi"</string>
     <string name="widgets_full_sheet_search_bar_hint" msgid="8484659090860596457">"Căutare"</string>
-    <string name="widgets_full_sheet_cancel_button_description" msgid="5766167035728653605">"Șterge textul din caseta de căutare"</string>
+    <string name="widgets_full_sheet_cancel_button_description" msgid="5766167035728653605">"Ștergeți textul din caseta de căutare"</string>
     <string name="no_widgets_available" msgid="4337693382501046170">"Widgeturile și comenzile rapide nu sunt disponibile"</string>
     <string name="no_search_results" msgid="3787956167293097509">"Nu au fost găsite widgeturi sau comenzi rapide"</string>
     <string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"Personale"</string>
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Serviciu"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Conversații"</string>
-    <string name="widget_education_header" msgid="4874760613775913787">"Informații utile la îndemâna ta"</string>
-    <string name="widget_education_content" msgid="1731667670753497052">"Pentru a primi informații fără să deschizi aplicațiile, poți adăuga widgeturi pe ecranul de pornire"</string>
-    <string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Atinge ca să schimbi setările pentru widgeturi"</string>
+    <string name="widget_education_header" msgid="4874760613775913787">"Informații utile la îndemâna dvs."</string>
+    <string name="widget_education_content" msgid="1731667670753497052">"Pentru a primi informații fără să deschideți aplicațiile, puteți adăuga widgeturi pe ecranul de pornire"</string>
+    <string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Atingeți ca să schimbați setările pentru widgeturi"</string>
     <string name="widget_education_close_button" msgid="8676165703104836580">"OK"</string>
-    <string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Modifică setările pentru widgeturi"</string>
-    <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Caută aplicații"</string>
+    <string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Modificați setările pentru widgeturi"</string>
+    <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Căutați 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="all_apps_search_market_message" msgid="1366263386197059176">"Căutați 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>
-    <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Atinge și ține apăsat ca să muți comanda rapidă."</string>
-    <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Atinge de două ori și ține apăsat pentru a muta o comandă rapidă sau folosește acțiuni personalizate."</string>
+    <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Atingeți și țineți apăsat pentru a muta comanda rapidă."</string>
+    <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Atingeți de două ori și țineți apăsat pentru a muta o comandă rapidă sau folosiți acțiuni personalizate."</string>
     <string name="out_of_space" msgid="6455557115204099579">"Nu există spațiu liber pe acest ecran de pornire"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Spațiu epuizat în bara Preferate"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Lista de aplicații"</string>
     <string name="all_apps_search_results" msgid="5889367432531296759">"Rezultatele căutării"</string>
     <string name="all_apps_button_personal_label" msgid="1315764287305224468">"Lista de aplicații personale"</string>
     <string name="all_apps_button_work_label" msgid="7270707118948892488">"Lista de aplicații de serviciu"</string>
-    <string name="remove_drop_target_label" msgid="7812859488053230776">"Elimină"</string>
-    <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Dezinstalează"</string>
+    <string name="remove_drop_target_label" msgid="7812859488053230776">"Eliminați"</string>
+    <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Dezinstalați"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Informații despre aplicații"</string>
-    <string name="install_drop_target_label" msgid="2539096853673231757">"Instalează"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Instalați"</string>
     <string name="dismiss_prediction_label" msgid="3357562989568808658">"Nu sugera aplicația"</string>
     <string name="pin_prediction" msgid="4196423321649756498">"Fixează predicția"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"instalează comenzi rapide"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Permite unei aplicații să adauge comenzi rapide fără intervenția utilizatorului."</string>
-    <string name="permlab_read_settings" msgid="5136500343007704955">"citește setările și comenzile rapide de pe ecranul de pornire"</string>
+    <string name="permlab_read_settings" msgid="5136500343007704955">"citiți setările și comenzile rapide de pe ecranul de pornire"</string>
     <string name="permdesc_read_settings" msgid="4208061150510996676">"Permite aplicației să citească setările și comenzile rapide de pe ecranul de pornire."</string>
-    <string name="permlab_write_settings" msgid="4820028712156303762">"scrie setările și comenzile rapide de pe ecranul de pornire"</string>
+    <string name="permlab_write_settings" msgid="4820028712156303762">"scrieți setările și comenzile rapide de pe ecranul de pornire"</string>
     <string name="permdesc_write_settings" msgid="726859348127868466">"Permite aplicației să modifice setările și comenzile rapide de pe ecranul de pornire."</string>
     <string name="msg_no_phone_permission" msgid="9208659281529857371">"<xliff:g id="APP_NAME">%1$s</xliff:g> nu are permisiunea de a apela"</string>
     <string name="gadget_error_text" msgid="740356548025791839">"Widgetul nu poate fi încărcat"</string>
     <string name="gadget_setup_text" msgid="8348374825537681407">"Setări pentru widget"</string>
-    <string name="gadget_complete_setup_text" msgid="309040266978007925">"Atinge pentru a finaliza configurarea"</string>
+    <string name="gadget_complete_setup_text" msgid="309040266978007925">"Atingeți pentru a finaliza configurarea"</string>
     <string name="uninstall_system_app_text" msgid="4172046090762920660">"Aceasta este o aplicație de sistem și nu poate fi dezinstalată."</string>
-    <string name="folder_hint_text" msgid="5174843001373488816">"Modifică numele"</string>
+    <string name="folder_hint_text" msgid="5174843001373488816">"Modificați numele"</string>
     <string name="disabled_app_label" msgid="6673129024321402780">"S-a dezactivat <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
     <string name="dotted_app_label" msgid="1865617679843363410">"{count,plural, =1{{app_name} are # notificare}few{{app_name} are # notificări}other{{app_name} are # de notificări}}"</string>
     <string name="default_scroll_format" msgid="7475544710230993317">"Pagina %1$d din %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Ecranul de pornire %1$d din %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Pagină nouă pe ecranul de pornire"</string>
     <string name="folder_opened" msgid="94695026776264709">"Dosar deschis, <xliff:g id="WIDTH">%1$d</xliff:g> pe <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
-    <string name="folder_tap_to_close" msgid="4625795376335528256">"Atinge pentru a închide dosarul"</string>
-    <string name="folder_tap_to_rename" msgid="4017685068016979677">"Atinge pentru a salva noul nume"</string>
+    <string name="folder_tap_to_close" msgid="4625795376335528256">"Atingeți pentru a închide dosarul"</string>
+    <string name="folder_tap_to_rename" msgid="4017685068016979677">"Atingeți pentru a salva noul nume"</string>
     <string name="folder_closed" msgid="4100806530910930934">"Dosar închis"</string>
     <string name="folder_renamed" msgid="1794088362165669656">"Dosar redenumit <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Dosar: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> elemente"</string>
@@ -105,58 +105,58 @@
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Imagine de fundal și stil"</string>
     <string name="settings_button_text" msgid="8873672322605444408">"Setări ecran de pornire"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"Dezactivată de administrator"</string>
-    <string name="allow_rotation_title" msgid="7222049633713050106">"Permite rotirea ecranului de pornire"</string>
+    <string name="allow_rotation_title" msgid="7222049633713050106">"Permiteți rotirea ecranului de pornire"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Când telefonul este rotit"</string>
     <string name="notification_dots_title" msgid="9062440428204120317">"Puncte de notificare"</string>
     <string name="notification_dots_desc_on" msgid="1679848116452218908">"Activate"</string>
     <string name="notification_dots_desc_off" msgid="1760796511504341095">"Dezactivate"</string>
     <string name="title_missing_notification_access" msgid="7503287056163941064">"Este necesar accesul la notificări"</string>
-    <string name="msg_missing_notification_access" msgid="281113995110910548">"Pentru a afișa punctele de notificare, activează notificările din aplicație pentru <xliff:g id="NAME">%1$s</xliff:g>"</string>
-    <string name="title_change_settings" msgid="1376365968844349552">"Modifică setările"</string>
-    <string name="notification_dots_service_title" msgid="4284221181793592871">"Afișează punctele de notificare"</string>
+    <string name="msg_missing_notification_access" msgid="281113995110910548">"Pentru a afișa punctele de notificare, activați notificările din aplicație pentru <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="title_change_settings" msgid="1376365968844349552">"Modificați setările"</string>
+    <string name="notification_dots_service_title" msgid="4284221181793592871">"Afișați punctele de notificare"</string>
     <string name="developer_options_title" msgid="700788437593726194">"Opțiuni dezvoltator"</string>
-    <string name="auto_add_shortcuts_label" msgid="4926805029653694105">"Adaugă pictograme de aplicații pe ecranul de pornire"</string>
+    <string name="auto_add_shortcuts_label" msgid="4926805029653694105">"Adăugați pictograme de aplicații pe ecranul de pornire"</string>
     <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Pentru aplicații noi"</string>
     <string name="package_state_unknown" msgid="7592128424511031410">"Necunoscut"</string>
-    <string name="abandoned_clean_this" msgid="7610119707847920412">"Elimină"</string>
-    <string name="abandoned_search" msgid="891119232568284442">"Caută"</string>
+    <string name="abandoned_clean_this" msgid="7610119707847920412">"Eliminați"</string>
+    <string name="abandoned_search" msgid="891119232568284442">"Căutați"</string>
     <string name="abandoned_promises_title" msgid="7096178467971716750">"Aplicația nu este instalată"</string>
-    <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Aplicația pentru această pictogramă nu este instalată. Poți să ștergi pictograma sau să cauți aplicația și s-o instalezi manual."</string>
+    <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Aplicația pentru această pictogramă nu este instalată. Puteți să ștergeți pictograma sau să căutați aplicația și s-o instalați manual."</string>
     <string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> se instalează, <xliff:g id="PROGRESS">%2$s</xliff:g> finalizat"</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> se descarcă (finalizat <xliff:g id="PROGRESS">%2$s</xliff:g>)"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> așteaptă instalarea"</string>
     <string name="dialog_update_title" msgid="114234265740994042">"Este necesară actualizarea aplicației"</string>
-    <string name="dialog_update_message" msgid="4176784553982226114">"Aplicația pentru această pictogramă nu este actualizată. Poți să actualizezi manual ca să reactivezi comanda rapidă sau să elimini pictograma."</string>
-    <string name="dialog_update" msgid="2178028071796141234">"Actualizează"</string>
-    <string name="dialog_remove" msgid="6510806469849709407">"Elimină"</string>
+    <string name="dialog_update_message" msgid="4176784553982226114">"Aplicația pentru această pictogramă nu este actualizată. Puteți să actualizați manual ca să reactivați comanda rapidă sau să eliminați pictograma."</string>
+    <string name="dialog_update" msgid="2178028071796141234">"Actualizați"</string>
+    <string name="dialog_remove" msgid="6510806469849709407">"Eliminați"</string>
     <string name="widgets_list" msgid="796804551140113767">"Listă de widgeturi"</string>
     <string name="widgets_list_closed" msgid="6141506579418771922">"Lista de widgeturi este închisă"</string>
-    <string name="action_add_to_workspace" msgid="215894119683164916">"Adaugă pe ecranul de pornire"</string>
-    <string name="action_move_here" msgid="2170188780612570250">"Mută elementul aici"</string>
+    <string name="action_add_to_workspace" msgid="215894119683164916">"Adăugați pe ecranul de pornire"</string>
+    <string name="action_move_here" msgid="2170188780612570250">"Mutați elementul aici"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Element adăugat pe ecranul de pornire"</string>
     <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_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="undo" msgid="4151576204245173321">"Anulați"</string>
+    <string name="action_move" msgid="4339390619886385032">"Mutați elementul"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Mutați în rândul <xliff:g id="NUMBER_0">%1$s</xliff:g> coloana <xliff:g id="NUMBER_1">%2$s</xliff:g> din <xliff:g id="STRING">%3$s</xliff:g>"</string>
+    <string name="move_to_position" msgid="6750008980455459790">"Mutați pe poziția <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
+    <string name="move_to_hotseat_position" msgid="6295412897075147808">"Mutați în preferate, pe poziția <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Element mutat"</string>
-    <string name="add_to_folder" msgid="9040534766770853243">"Adaugă în dosar: <xliff:g id="NAME">%1$s</xliff:g>"</string>
-    <string name="add_to_folder_with_app" msgid="4534929978967147231">"Adaugă în dosarul cu <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="add_to_folder" msgid="9040534766770853243">"Adăugați în dosar: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="add_to_folder_with_app" msgid="4534929978967147231">"Adăugați în dosarul cu <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="added_to_folder" msgid="4793259502305558003">"Element adăugat în dosar"</string>
-    <string name="create_folder_with" msgid="4050141361160214248">"Creează dosar cu: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="create_folder_with" msgid="4050141361160214248">"Creați dosar cu: <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_created" msgid="6409794597405184510">"Dosar creat"</string>
-    <string name="action_move_to_workspace" msgid="39528912300293768">"Mută pe ecranul de pornire"</string>
-    <string name="action_resize" msgid="1802976324781771067">"Redimensionează"</string>
-    <string name="action_increase_width" msgid="8773715375078513326">"Crește lățimea"</string>
-    <string name="action_increase_height" msgid="459390020612501122">"Crește înălțimea"</string>
-    <string name="action_decrease_width" msgid="1374549771083094654">"Redu lățimea"</string>
-    <string name="action_decrease_height" msgid="282377193880900022">"Redu înălțimea"</string>
+    <string name="action_move_to_workspace" msgid="39528912300293768">"Mutați pe ecranul de pornire"</string>
+    <string name="action_resize" msgid="1802976324781771067">"Redimensionați"</string>
+    <string name="action_increase_width" msgid="8773715375078513326">"Creșteți lățimea"</string>
+    <string name="action_increase_height" msgid="459390020612501122">"Creșteți înălțimea"</string>
+    <string name="action_decrease_width" msgid="1374549771083094654">"Reduceți lățimea"</string>
+    <string name="action_decrease_height" msgid="282377193880900022">"Reduceți înălțimea"</string>
     <string name="widget_resized" msgid="9130327887929620">"Widgetul a fost redimensionat la lățimea <xliff:g id="NUMBER_0">%1$s</xliff:g> și înălțimea <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
     <string name="action_deep_shortcut" msgid="2864038805849372848">"Comenzi rapide"</string>
     <string name="shortcuts_menu_with_notifications_description" msgid="2676582286544232849">"Comenzi rapide și notificări"</string>
-    <string name="action_dismiss_notification" msgid="5909461085055959187">"Închide"</string>
-    <string name="accessibility_close" msgid="2277148124685870734">"Închide"</string>
+    <string name="action_dismiss_notification" msgid="5909461085055959187">"Închideți"</string>
+    <string name="accessibility_close" msgid="2277148124685870734">"Închideți"</string>
     <string name="notification_dismissed" msgid="6002233469409822874">"Notificare închisă"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Personale"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Profesionale"</string>
@@ -164,14 +164,14 @@
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Aplicațiile pentru lucru sunt marcate și vizibile pentru administratorul IT"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"OK"</string>
     <string name="work_apps_paused_title" msgid="3040901117349444598">"Aplicațiile pentru lucru sunt întrerupte"</string>
-    <string name="work_apps_paused_body" msgid="261634750995824906">"Aplicațiile pentru lucru nu pot să-ți trimită notificări, să folosească bateria sau să-ți acceseze locația"</string>
-    <string name="work_apps_paused_content_description" msgid="5149623040804051095">"Aplicațiile pentru lucru sunt dezactivate. Acestea nu pot să-ți trimită notificări, să folosească bateria sau să-ți acceseze locația."</string>
+    <string name="work_apps_paused_body" msgid="261634750995824906">"Aplicațiile pentru lucru nu pot să vă trimită notificări, să folosească bateria sau să vă acceseze locația"</string>
+    <string name="work_apps_paused_content_description" msgid="5149623040804051095">"Aplicațiile pentru lucru sunt dezactivate. Acestea nu pot să vă trimită notificări, să folosească bateria sau să vă acceseze locația."</string>
     <string name="work_apps_paused_edu_banner" msgid="8872412121608402058">"Aplicațiile pentru lucru sunt marcate și vizibile pentru administratorul IT"</string>
     <string name="work_apps_paused_edu_accept" msgid="6377476824357318532">"OK"</string>
-    <string name="work_apps_pause_btn_text" msgid="4669288269140620646">"Întrerupe aplicațiile pentru lucru"</string>
-    <string name="work_apps_enable_btn_text" msgid="1156432622148413741">"Activează aplicațiile pentru lucru"</string>
+    <string name="work_apps_pause_btn_text" msgid="4669288269140620646">"Întrerupeți aplicațiile pentru lucru"</string>
+    <string name="work_apps_enable_btn_text" msgid="1156432622148413741">"Activați aplicațiile pentru lucru"</string>
     <string name="developer_options_filter_hint" msgid="5896817443635989056">"Filtru"</string>
-    <string name="search_pref_screen_title" msgid="3258959643336315962">"Caută pe telefon"</string>
-    <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Caută pe tabletă"</string>
+    <string name="search_pref_screen_title" msgid="3258959643336315962">"Căutați pe telefon"</string>
+    <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Căutați pe tabletă"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Eșuare: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
 </resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 9733ac6..0ad12bd 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Объект убран."</string>
     <string name="undo" msgid="4151576204245173321">"Отменить"</string>
     <string name="action_move" msgid="4339390619886385032">"Переместить элемент"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Переместить в ячейку <xliff:g id="NUMBER_0">%1$s</xliff:g> <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Переместите в строку <xliff:g id="NUMBER_0">%1$s</xliff:g> столбца <xliff:g id="NUMBER_1">%2$s</xliff:g> на экране \"<xliff:g id="STRING">%3$s</xliff:g>\""</string>
     <string name="move_to_position" msgid="6750008980455459790">"Переместить в позицию <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Переместить в Избранное (<xliff:g id="NUMBER">%1$s</xliff:g>)"</string>
     <string name="item_moved" msgid="4606538322571412879">"Элемент перемещен."</string>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index e160e81..cf3dc64 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"අයිතමය ඉවත් කරන ලදි"</string>
     <string name="undo" msgid="4151576204245173321">"අස් කරන්න"</string>
     <string name="action_move" msgid="4339390619886385032">"අයිතමය ගෙනයන්න"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"පේළිය <xliff:g id="NUMBER_0">%1$s</xliff:g> තීරුව <xliff:g id="NUMBER_1">%2$s</xliff:g> වෙත ගෙන යන්න"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g> තුළ <xliff:g id="NUMBER_0">%1$s</xliff:g> තීරුවේ<xliff:g id="NUMBER_1">%2$s</xliff:g> පේළියට යන්න"</string>
     <string name="move_to_position" msgid="6750008980455459790">"<xliff:g id="NUMBER">%1$s</xliff:g> ස්ථානය වෙත ගෙන යන්න"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"ප්‍රියතම ස්ථානය <xliff:g id="NUMBER">%1$s</xliff:g> වෙත ගෙන යන්න"</string>
     <string name="item_moved" msgid="4606538322571412879">"අයිතමය ගෙන යන ලදි"</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 8582baf..e3bcc52 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Položka bola odstránená"</string>
     <string name="undo" msgid="4151576204245173321">"Späť"</string>
     <string name="action_move" msgid="4339390619886385032">"Presunúť položku"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Presunúť do stĺpca <xliff:g id="NUMBER_1">%2$s</xliff:g> v riadku <xliff:g id="NUMBER_0">%1$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Premiestnite položku do <xliff:g id="NUMBER_0">%1$s</xliff:g>. riadka <xliff:g id="NUMBER_1">%2$s</xliff:g>. stĺpca na obrazovke <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Presunúť na <xliff:g id="NUMBER">%1$s</xliff:g>. miesto"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Presunúť na <xliff:g id="NUMBER">%1$s</xliff:g>. miesto v obľúbených položkách"</string>
     <string name="item_moved" msgid="4606538322571412879">"Položka bola presunutá"</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 0762cf7..5f952cf 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Element je bil odstranjen."</string>
     <string name="undo" msgid="4151576204245173321">"Razveljavi"</string>
     <string name="action_move" msgid="4339390619886385032">"Premik elementa"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Premik v <xliff:g id="NUMBER_0">%1$s</xliff:g>. vrstico <xliff:g id="NUMBER_1">%2$s</xliff:g>. stolpca"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Premik v vrstico <xliff:g id="NUMBER_0">%1$s</xliff:g> v stolpcu <xliff:g id="NUMBER_1">%2$s</xliff:g> na »<xliff:g id="STRING">%3$s</xliff:g>«"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Premk na mesto št. <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Premik na mesto priljubljenih št. <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Element je premaknjen"</string>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index 821bd50..9f29a87 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Artikulli u hoq"</string>
     <string name="undo" msgid="4151576204245173321">"Zhbëj"</string>
     <string name="action_move" msgid="4339390619886385032">"Zhvendose artikullin"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Zhvendos te rreshti <xliff:g id="NUMBER_0">%1$s</xliff:g>, kolona <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Kalo te rreshti <xliff:g id="NUMBER_0">%1$s</xliff:g> kolona <xliff:g id="NUMBER_1">%2$s</xliff:g> në <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Zhvendos te pozicioni <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Zhvendos te pozicioni <xliff:g id="NUMBER">%1$s</xliff:g> i preferencave"</string>
     <string name="item_moved" msgid="4606538322571412879">"Artikulli u zhvendos"</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 85cc281..948d9b6 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Ставка је уклоњена"</string>
     <string name="undo" msgid="4151576204245173321">"Опозови"</string>
     <string name="action_move" msgid="4339390619886385032">"Премести ставку"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Премести у ред <xliff:g id="NUMBER_0">%1$s</xliff:g> и колону <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Преместите у ред <xliff:g id="NUMBER_0">%1$s</xliff:g> колону <xliff:g id="NUMBER_1">%2$s</xliff:g> на <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Премести на <xliff:g id="NUMBER">%1$s</xliff:g>. позицију"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Премести на <xliff:g id="NUMBER">%1$s</xliff:g>. позицију у омиљеним"</string>
     <string name="item_moved" msgid="4606538322571412879">"Ставка је премештена"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 7525eec..c35adb0 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Objektet har tagits bort"</string>
     <string name="undo" msgid="4151576204245173321">"Ångra"</string>
     <string name="action_move" msgid="4339390619886385032">"Flytta objekt"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Flytta till rad <xliff:g id="NUMBER_0">%1$s</xliff:g>, kolumn <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Flytta till rad <xliff:g id="NUMBER_0">%1$s</xliff:g> kolumn <xliff:g id="NUMBER_1">%2$s</xliff:g> i <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Flytta till plats <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Flytta till favoritplats <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Objektet har flyttats"</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 3ae5dcf..c233cc4 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Kipengee kimeondolewa"</string>
     <string name="undo" msgid="4151576204245173321">"Tendua"</string>
     <string name="action_move" msgid="4339390619886385032">"Hamisha kipengee"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Hamishia safu mlalo <xliff:g id="NUMBER_0">%1$s</xliff:g> safu wima <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Hamishia safu mlalo <xliff:g id="NUMBER_0">%1$s</xliff:g> safu wima <xliff:g id="NUMBER_1">%2$s</xliff:g> kwenye <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Hamishia nafasi ya <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Hamishia nafasi inayopendwa ya <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Kipengee kimesogezwa"</string>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 0547fc2..bfec460 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"அகற்றப்பட்டது"</string>
     <string name="undo" msgid="4151576204245173321">"செயல்தவிர்"</string>
     <string name="action_move" msgid="4339390619886385032">"நகர்த்து"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"<xliff:g id="NUMBER_0">%1$s</xliff:g> வரிசை, <xliff:g id="NUMBER_1">%2$s</xliff:g> நெடுவரிசைக்கு நகர்த்து"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g> என்பதில் <xliff:g id="NUMBER_0">%1$s</xliff:g>வது வரிசை <xliff:g id="NUMBER_1">%2$s</xliff:g>வது நெடுவரிசைக்கு நகர்த்தப்படும்"</string>
     <string name="move_to_position" msgid="6750008980455459790">"நிலை <xliff:g id="NUMBER">%1$s</xliff:g>க்கு நகர்த்து"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"விரும்பும் நிலை <xliff:g id="NUMBER">%1$s</xliff:g>க்கு நகர்த்து"</string>
     <string name="item_moved" msgid="4606538322571412879">"உருப்படி நகர்த்தப்பட்டது"</string>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 5c89925..8b42a8f 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -71,7 +71,7 @@
     <string name="all_apps_search_results" msgid="5889367432531296759">"సెర్చ్ ఫలితాలు"</string>
     <string name="all_apps_button_personal_label" msgid="1315764287305224468">"వ్యక్తిగత యాప్‌ల లిస్ట్‌"</string>
     <string name="all_apps_button_work_label" msgid="7270707118948892488">"కార్యాలయ యాప్‌ల లిస్ట్‌"</string>
-    <string name="remove_drop_target_label" msgid="7812859488053230776">"తీసివేయండి"</string>
+    <string name="remove_drop_target_label" msgid="7812859488053230776">"తీసివేయి"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"అన్ఇన్‌స్టాల్ చేయండి"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"యాప్ సమాచారం"</string>
     <string name="install_drop_target_label" msgid="2539096853673231757">"ఇన్‌స్టాల్ చేయండి"</string>
@@ -118,7 +118,7 @@
     <string name="auto_add_shortcuts_label" msgid="4926805029653694105">"యాప్ చిహ్నాలను మొదటి స్క్రీన్‌కు జోడించండి"</string>
     <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"కొత్త యాప్‌ల కోసం"</string>
     <string name="package_state_unknown" msgid="7592128424511031410">"తెలియదు"</string>
-    <string name="abandoned_clean_this" msgid="7610119707847920412">"తీసివేయండి"</string>
+    <string name="abandoned_clean_this" msgid="7610119707847920412">"తీసివేయి"</string>
     <string name="abandoned_search" msgid="891119232568284442">"సెర్చ్"</string>
     <string name="abandoned_promises_title" msgid="7096178467971716750">"ఈ యాప్ ఇన్‌స్టాల్ చేయబడలేదు"</string>
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"ఈ ఐకాన్‌కు చెందిన యాప్ ఇన్‌స్టాల్ చేయలేదు. మీరు దీన్ని తీసివేయవచ్చు లేదా ఆ యాప్ కోసం సెర్చ్ చేసి, దాన్ని మాన్యువల్‌గా ఇన్‌స్టాల్ చేయవచ్చు."</string>
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"ఐటెమ్ తీసివేయబడింది"</string>
     <string name="undo" msgid="4151576204245173321">"చర్య రద్దు"</string>
     <string name="action_move" msgid="4339390619886385032">"అంశాన్ని తరలించు"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"అడ్డు వరుస <xliff:g id="NUMBER_0">%1$s</xliff:g> నిలువు వరుస <xliff:g id="NUMBER_1">%2$s</xliff:g>కి తరలించు"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g> లో <xliff:g id="NUMBER_0">%1$s</xliff:g> అడ్డు వరుస <xliff:g id="NUMBER_1">%2$s</xliff:g>నిలువు వరుసకు తరలించండి"</string>
     <string name="move_to_position" msgid="6750008980455459790">"<xliff:g id="NUMBER">%1$s</xliff:g>వ స్థానానికి తరలించు"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"ఇష్టమైనవిలో <xliff:g id="NUMBER">%1$s</xliff:g>వ స్థానానికి తరలించు"</string>
     <string name="item_moved" msgid="4606538322571412879">"అంశం తరలించబడింది"</string>
@@ -155,7 +155,7 @@
     <string name="widget_resized" msgid="9130327887929620">"విడ్జెట్ పరిమాణం వెడల్పు <xliff:g id="NUMBER_0">%1$s</xliff:g>కి, ఎత్తు <xliff:g id="NUMBER_1">%2$s</xliff:g>కి మార్చబడింది"</string>
     <string name="action_deep_shortcut" msgid="2864038805849372848">"షార్ట్‌కట్స్"</string>
     <string name="shortcuts_menu_with_notifications_description" msgid="2676582286544232849">"షార్ట్‌కట్‌లు మరియు నోటిఫికేషన్‌లు"</string>
-    <string name="action_dismiss_notification" msgid="5909461085055959187">"తీసివేయండి"</string>
+    <string name="action_dismiss_notification" msgid="5909461085055959187">"తీసివేయి"</string>
     <string name="accessibility_close" msgid="2277148124685870734">"మూసివేస్తుంది"</string>
     <string name="notification_dismissed" msgid="6002233469409822874">"నోటిఫికేషన్ తీసివేయబడింది"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"వ్యక్తిగతం"</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 07e5796..f633b5f 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"นำรายการออกแล้ว"</string>
     <string name="undo" msgid="4151576204245173321">"เลิกทำ"</string>
     <string name="action_move" msgid="4339390619886385032">"ย้ายรายการ"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"ย้ายไปที่แถว <xliff:g id="NUMBER_0">%1$s</xliff:g> คอลัมน์ <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"ย้ายไปยังแถว <xliff:g id="NUMBER_0">%1$s</xliff:g> คอลัมน์ <xliff:g id="NUMBER_1">%2$s</xliff:g> ใน <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"ย้ายไปยังตำแหน่ง <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"ย้ายไปยังตำแหน่งที่ <xliff:g id="NUMBER">%1$s</xliff:g> ของรายการโปรด"</string>
     <string name="item_moved" msgid="4606538322571412879">"ย้ายรายการแล้ว"</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index f7158b4..de4f952 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Naalis na ang item"</string>
     <string name="undo" msgid="4151576204245173321">"I-undo"</string>
     <string name="action_move" msgid="4339390619886385032">"Ilipat ang item"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Ilipat sa row <xliff:g id="NUMBER_0">%1$s</xliff:g> column <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Pumunta sa row <xliff:g id="NUMBER_0">%1$s</xliff:g> column <xliff:g id="NUMBER_1">%2$s</xliff:g> sa <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Ilipat sa posisyon <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Ilipat sa posisyon <xliff:g id="NUMBER">%1$s</xliff:g> sa mga paborito"</string>
     <string name="item_moved" msgid="4606538322571412879">"Nalipat ang item"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index b12b9b1..f41284b 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Öğe silindi"</string>
     <string name="undo" msgid="4151576204245173321">"Geri al"</string>
     <string name="action_move" msgid="4339390619886385032">"Öğeyi taşı"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"<xliff:g id="NUMBER_0">%1$s</xliff:g>. satır <xliff:g id="NUMBER_1">%2$s</xliff:g>. sütuna taşı"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g> sayfasında <xliff:g id="NUMBER_0">%1$s</xliff:g>. satır <xliff:g id="NUMBER_1">%2$s</xliff:g>. sütuna taşı"</string>
     <string name="move_to_position" msgid="6750008980455459790">"<xliff:g id="NUMBER">%1$s</xliff:g>. sıraya taşı"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Favorilerde <xliff:g id="NUMBER">%1$s</xliff:g>. sıraya taşı"</string>
     <string name="item_moved" msgid="4606538322571412879">"Öğe taşındı"</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 2b43a4d..360950e 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Елемент вилучено"</string>
     <string name="undo" msgid="4151576204245173321">"Відмінити"</string>
     <string name="action_move" msgid="4339390619886385032">"Перемістити елемент"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Перемістити в рядок <xliff:g id="NUMBER_0">%1$s</xliff:g>, стовпець <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Перенести: <xliff:g id="STRING">%3$s</xliff:g>, рядок <xliff:g id="NUMBER_0">%1$s</xliff:g>, стовпець <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Перемістити на <xliff:g id="NUMBER">%1$s</xliff:g> місце"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Перемістити у вибране на <xliff:g id="NUMBER">%1$s</xliff:g> місце"</string>
     <string name="item_moved" msgid="4606538322571412879">"Елемент переміщено"</string>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index 70a853f..e68e43e 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"آئٹم ہٹا دیا گیا"</string>
     <string name="undo" msgid="4151576204245173321">"کالعدم کریں"</string>
     <string name="action_move" msgid="4339390619886385032">"آئٹم منتقل کریں"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"قطار <xliff:g id="NUMBER_0">%1$s</xliff:g> کالم <xliff:g id="NUMBER_1">%2$s</xliff:g> میں منتقل کریں"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g> میں قطار <xliff:g id="NUMBER_0">%1$s</xliff:g> کالم <xliff:g id="NUMBER_1">%2$s</xliff:g> میں منتقل کریں"</string>
     <string name="move_to_position" msgid="6750008980455459790">"پوزیشن <xliff:g id="NUMBER">%1$s</xliff:g> میں منتقل کریں"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"پسندیدہ پوزیشن <xliff:g id="NUMBER">%1$s</xliff:g> میں منتقل کریں"</string>
     <string name="item_moved" msgid="4606538322571412879">"آئٹم منتقل کر دیا گیا"</string>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index 57a446b..9c7ed56 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Element olib tashlandi"</string>
     <string name="undo" msgid="4151576204245173321">"Qaytarish"</string>
     <string name="action_move" msgid="4339390619886385032">"Obyektni ko‘chirib o‘tkazish"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"<xliff:g id="NUMBER_0">%1$s</xliff:g> <xliff:g id="NUMBER_1">%2$s</xliff:g> katakka olish"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g> ichidagi <xliff:g id="NUMBER_0">%1$s</xliff:g>-qator <xliff:g id="NUMBER_1">%2$s</xliff:g>-ustuniga oʻting"</string>
     <string name="move_to_position" msgid="6750008980455459790">"<xliff:g id="NUMBER">%1$s</xliff:g>-joyga olish"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Sevimlilarga olish (<xliff:g id="NUMBER">%1$s</xliff:g>)"</string>
     <string name="item_moved" msgid="4606538322571412879">"Element ko‘chirib o‘tkazildi"</string>
diff --git a/res/values-v31/colors.xml b/res/values-v31/colors.xml
index 7bbdbd1..7a75ddb 100644
--- a/res/values-v31/colors.xml
+++ b/res/values-v31/colors.xml
@@ -56,4 +56,9 @@
 
     <color name="workspace_accent_color_light">@android:color/system_accent1_100</color>
     <color name="workspace_accent_color_dark">@android:color/system_accent2_600</color>
+
+    <color name="preload_icon_accent_color_light">@android:color/system_accent1_600</color>
+    <color name="preload_icon_background_color_light">@android:color/system_accent2_200</color>
+    <color name="preload_icon_accent_color_dark">@android:color/system_accent1_300</color>
+    <color name="preload_icon_background_color_dark">@android:color/system_neutral2_700</color>
 </resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 2c81ae7..30167fa 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Đã xóa mục"</string>
     <string name="undo" msgid="4151576204245173321">"Hủy"</string>
     <string name="action_move" msgid="4339390619886385032">"Di chuyển mục"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Di chuyển đến hàng <xliff:g id="NUMBER_0">%1$s</xliff:g> cột <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Di chuyển đến hàng <xliff:g id="NUMBER_0">%1$s</xliff:g> cột <xliff:g id="NUMBER_1">%2$s</xliff:g> tại <xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Di chuyển tới vị trí <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Di chuyển tới vị trí mục yêu thích <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Đã di chuyển mục"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 7c104e2..141d165 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"项目已移除"</string>
     <string name="undo" msgid="4151576204245173321">"撤消"</string>
     <string name="action_move" msgid="4339390619886385032">"移动项目"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"移至第 <xliff:g id="NUMBER_0">%1$s</xliff:g> 行第 <xliff:g id="NUMBER_1">%2$s</xliff:g> 列"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"移至<xliff:g id="STRING">%3$s</xliff:g>中的第 <xliff:g id="NUMBER_0">%1$s</xliff:g> 行第 <xliff:g id="NUMBER_1">%2$s</xliff:g> 列"</string>
     <string name="move_to_position" msgid="6750008980455459790">"移至第 <xliff:g id="NUMBER">%1$s</xliff:g> 个位置"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"移至收藏夹第 <xliff:g id="NUMBER">%1$s</xliff:g> 个位置"</string>
     <string name="item_moved" msgid="4606538322571412879">"已移动项目"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index ed288d6..2ff233b 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"項目已移除"</string>
     <string name="undo" msgid="4151576204245173321">"復原"</string>
     <string name="action_move" msgid="4339390619886385032">"移動項目"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"移動至第 <xliff:g id="NUMBER_0">%1$s</xliff:g> 行第 <xliff:g id="NUMBER_1">%2$s</xliff:g> 列"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"移去 <xliff:g id="STRING">%3$s</xliff:g> 嘅第 <xliff:g id="NUMBER_0">%1$s</xliff:g> 列,第 <xliff:g id="NUMBER_1">%2$s</xliff:g> 欄"</string>
     <string name="move_to_position" msgid="6750008980455459790">"移動至位置 <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"移動至喜愛的位置 <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"已移動項目"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 3690c3a..91cfd82 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"已移除項目"</string>
     <string name="undo" msgid="4151576204245173321">"復原"</string>
     <string name="action_move" msgid="4339390619886385032">"移動項目"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"移至第 <xliff:g id="NUMBER_0">%1$s</xliff:g> 列第 <xliff:g id="NUMBER_1">%2$s</xliff:g> 欄"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"移動至 <xliff:g id="STRING">%3$s</xliff:g> 的第 <xliff:g id="NUMBER_0">%1$s</xliff:g> 列,第 <xliff:g id="NUMBER_1">%2$s</xliff:g> 欄"</string>
     <string name="move_to_position" msgid="6750008980455459790">"移至位置 <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"移至收藏位置 <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"已移動項目"</string>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 26e7dcf..12e8568 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -137,7 +137,7 @@
     <string name="item_removed" msgid="851119963877842327">"Into isusiwe"</string>
     <string name="undo" msgid="4151576204245173321">"Susa"</string>
     <string name="action_move" msgid="4339390619886385032">"Hambisa into"</string>
-    <string name="move_to_empty_cell" msgid="2833711483015685619">"Hambisa kurowu engu-<xliff:g id="NUMBER_0">%1$s</xliff:g> ikholomu engu-<xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_empty_cell_description" msgid="5254852678218206889">"Hamba emugqeni <xliff:g id="NUMBER_0">%1$s</xliff:g> ikholomu <xliff:g id="NUMBER_1">%2$s</xliff:g> ku-<xliff:g id="STRING">%3$s</xliff:g>"</string>
     <string name="move_to_position" msgid="6750008980455459790">"Hambisa kusimo esingu-<xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"Hambisa kusimo sezintandokazi esingu-<xliff:g id="NUMBER">%1$s</xliff:g>"</string>
     <string name="item_moved" msgid="4606538322571412879">"Into ihanjisiwe"</string>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 13f20c2..e5b588c 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" />
 
@@ -252,9 +258,10 @@
         if not specified -->
         <attr name="borderSpaceTwoPanelLandscapeVertical" format="float" />
 
-        <!-- These min cell values are only used if GridDisplayOption#isScalable is true -->
-        <!-- defaults to minCellHeight, if not specified -->
+        <!-- defaults to minCellHeight if not specified when GridDisplayOption#isScalable is true.
+         Must be defined when GridDisplayOption#isScalable is false. -->
         <attr name="allAppsCellHeight" format="float" />
+        <!-- These min cell values are only used if GridDisplayOption#isScalable is true -->
         <!-- defaults to minCellWidth, if not specified -->
         <attr name="allAppsCellWidth" format="float" />
         <!-- defaults to allAppsCellHeight, if not specified -->
@@ -326,6 +333,24 @@
         <!-- defaults to hotseatBorderSpace, if not specified -->
         <attr name="hotseatBorderSpaceTwoPanelPortrait" format="float" />
 
+        <!-- defaults to res.hotseat_bar_bottom_space_default, if not specified -->
+        <attr name="hotseatBarBottomSpace" format="float" />
+        <!-- defaults to hotseatBarBottomSpace, if not specified -->
+        <attr name="hotseatBarBottomSpaceLandscape" format="float" />
+        <!-- defaults to hotseatBarBottomSpace, if not specified -->
+        <attr name="hotseatBarBottomSpaceTwoPanelLandscape" format="float" />
+        <!-- defaults to hotseatBarBottomSpace, if not specified -->
+        <attr name="hotseatBarBottomSpaceTwoPanelPortrait" format="float" />
+
+        <!-- defaults to res.hotseat_qsb_space_default, if not specified -->
+        <attr name="hotseatQsbSpace" format="float" />
+        <!-- defaults to hotseatQsbSpace, if not specified -->
+        <attr name="hotseatQsbSpaceLandscape" format="float" />
+        <!-- defaults to hotseatQsbSpace, if not specified -->
+        <attr name="hotseatQsbSpaceTwoPanelLandscape" format="float" />
+        <!-- defaults to hotseatQsbSpace, if not specified -->
+        <attr name="hotseatQsbSpaceTwoPanelPortrait" format="float" />
+
         <attr name="iconImageSize" format="float" />
         <!-- defaults to iconImageSize, if not specified -->
         <attr name="iconSizeLandscape" format="float" />
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 2bc9239..309a1c5 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -85,4 +85,9 @@
     <color name="all_apps_button_color_2">#00677E</color>
     <color name="all_apps_button_color_3">#5F757E</color>
     <color name="all_apps_button_color_4">#005A6E</color>
+
+    <color name="preload_icon_accent_color_light">#00668B</color>
+    <color name="preload_icon_background_color_light">#B5CAD7</color>
+    <color name="preload_icon_accent_color_dark">#4BB6E8</color>
+    <color name="preload_icon_background_color_dark">#40484D</color>
 </resources>
diff --git a/res/values/config.xml b/res/values/config.xml
index 9aa1f03..3f94c34 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -130,6 +130,11 @@
     <item type="id" name="search_container_all_apps" />
     <item type="id" name="search_container_hotseat" />
 
+    <!-- Scalable Grid configuration -->
+    <!-- This is a float because it is converted to dp later in DeviceProfile -->
+    <dimen name="hotseat_bar_bottom_space_default">48</dimen>
+    <dimen name="hotseat_qsb_space_default">0</dimen>
+
     <!-- Recents -->
     <item type="id" name="overview_panel"/>
 
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 8403af4..dc80bc3 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>
@@ -364,9 +356,13 @@
     <dimen name="taskbar_size">0dp</dimen>
     <dimen name="taskbar_stashed_size">0dp</dimen>
     <dimen name="qsb_widget_height">0dp</dimen>
+    <dimen name="qsb_shadow_height">0dp</dimen>
     <dimen name="taskbar_icon_size">44dp</dimen>
     <!-- Note that this applies to both sides of all icons, so visible space is double this. -->
     <dimen name="taskbar_icon_spacing">8dp</dimen>
+    <dimen name="taskbar_nav_buttons_size">0dp</dimen>
+    <dimen name="taskbar_contextual_button_margin">0dp</dimen>
+    <dimen name="taskbar_hotseat_nav_spacing">0dp</dimen>
 
     <!-- Size of the maximum radius for the enforced rounded rectangles. -->
     <dimen name="enforced_rounded_corner_max_radius">16dp</dimen>
@@ -387,12 +383,24 @@
     <dimen name="overview_grid_side_margin">0dp</dimen>
     <dimen name="overview_grid_row_spacing">0dp</dimen>
     <dimen name="overview_page_spacing">0dp</dimen>
+    <dimen name="overview_minimum_next_prev_size">0dp</dimen>
+    <item name="overview_max_scale" format="float" type="dimen">0.0</item>
+    <item name="overview_modal_max_scale" format="float" type="dimen">0.0</item>
     <dimen name="split_placeholder_size">72dp</dimen>
     <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 +413,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..2addf50 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -344,7 +344,7 @@
     <string name="action_move">Move item</string>
 
     <!-- Accessibility description to move item to empty cell. -->
-    <string name="move_to_empty_cell">Move to row <xliff:g id="number" example="1">%1$s</xliff:g> column <xliff:g id="number" example="1">%2$s</xliff:g></string>
+    <string name="move_to_empty_cell_description">Move to row <xliff:g id="number" example="1">%1$s</xliff:g> column <xliff:g id="number" example="1">%2$s</xliff:g> in <xliff:g id="string" example="Home screen 2 of 4">%3$s</xliff:g></string>
 
     <!-- Accessibility description to move item inside a folder. -->
     <string name="move_to_position">Move to position <xliff:g id="number" example="1">%1$s</xliff:g></string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 2109510..a8cbf08 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>
diff --git a/res/xml/device_profiles.xml b/res/xml/device_profiles.xml
index 0802552..5ee291b 100644
--- a/res/xml/device_profiles.xml
+++ b/res/xml/device_profiles.xml
@@ -199,6 +199,8 @@
             launcher:allAppsBorderSpaceLandscape="16"
             launcher:hotseatBorderSpace="58"
             launcher:hotseatBorderSpaceLandscape="50.4"
+            launcher:hotseatBarBottomSpace="76"
+            launcher:hotseatBarBottomSpaceLandscape="40"
             launcher:canBeDefault="true" />
 
     </grid-option>
diff --git a/res/xml/paddings_6x5.xml b/res/xml/paddings_6x5.xml
index a72f554..2f421b7 100644
--- a/res/xml/paddings_6x5.xml
+++ b/res/xml/paddings_6x5.xml
@@ -17,60 +17,29 @@
 
 <device-paddings xmlns:launcher="http://schemas.android.com/apk/res-auto" >
 
-    <!--  Some non default screen sizes  -->
     <device-padding
-        launcher:maxEmptySpace="30dp">
+        launcher:maxEmptySpace="100dp">
         <workspaceTopPadding
-            launcher:a="0.34"
+            launcher:a="0.31"
             launcher:b="0"/>
         <workspaceBottomPadding
-            launcher:a="0.26"
+            launcher:a="0.69"
             launcher:b="0"/>
         <hotseatBottomPadding
-            launcher:a="0.4"
-            launcher:b="0"/>
-    </device-padding>
-
-    <device-padding
-        launcher:maxEmptySpace="170dp">
-        <workspaceTopPadding
             launcher:a="0"
-            launcher:b="20dp"/>
-        <workspaceBottomPadding
-            launcher:a="0.4"
-            launcher:b="0"
-            launcher:c="20dp"/>
-        <hotseatBottomPadding
-            launcher:a="0.6"
-            launcher:b="0"
-            launcher:c="20dp"/>
-    </device-padding>
-
-    <device-padding
-        launcher:maxEmptySpace="410dp">
-        <workspaceTopPadding
-            launcher:a="0"
-            launcher:b="112dp"/>
-        <workspaceBottomPadding
-            launcher:a="0.4"
-            launcher:b="0"
-            launcher:c="112dp"/>
-        <hotseatBottomPadding
-            launcher:a="0.6"
-            launcher:b="0"
-            launcher:c="112dp"/>
+            launcher:b="0"/>
     </device-padding>
 
     <device-padding
         launcher:maxEmptySpace="9999dp">
         <workspaceTopPadding
-            launcher:a="0.40"
-            launcher:c="36dp"/>
+            launcher:a="0.48"
+            launcher:b="0"/>
         <workspaceBottomPadding
-            launcher:a="0.60"
-            launcher:c="36dp"/>
+            launcher:a="0.52"
+            launcher:b="0"/>
         <hotseatBottomPadding
             launcher:a="0"
-            launcher:b="36dp"/>
+            launcher:b="0"/>
     </device-padding>
 </device-paddings>
\ No newline at end of file
diff --git a/src/com/android/launcher3/Alarm.java b/src/com/android/launcher3/Alarm.java
index d5b434c..e4aebf6 100644
--- a/src/com/android/launcher3/Alarm.java
+++ b/src/com/android/launcher3/Alarm.java
@@ -30,6 +30,7 @@
     private Handler mHandler;
     private OnAlarmListener mAlarmListener;
     private boolean mAlarmPending = false;
+    private long mLastSetTimeout;
 
     public Alarm() {
         mHandler = new Handler();
@@ -46,6 +47,7 @@
         mAlarmPending = true;
         long oldTriggerTime = mAlarmTriggerTime;
         mAlarmTriggerTime = currentTime + millisecondsInFuture;
+        mLastSetTimeout = millisecondsInFuture;
 
         // If the previous alarm was set for a longer duration, cancel it.
         if (mWaitingForCallback && oldTriggerTime > mAlarmTriggerTime) {
@@ -84,4 +86,9 @@
     public boolean alarmPending() {
         return mAlarmPending;
     }
+
+    /** Returns the last value passed to {@link #setAlarm(long)} */
+    public long getLastSetTimeout() {
+        return mLastSetTimeout;
+    }
 }
diff --git a/src/com/android/launcher3/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..d34f535 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -77,8 +77,8 @@
             new ArrayList<>();
 
     protected DeviceProfile mDeviceProfile;
-    protected StatsLogManager mStatsLogManager;
     protected SystemUiController mSystemUiController;
+    private StatsLogManager mStatsLogManager;
 
 
     public static final int ACTIVITY_STATE_STARTED = 1 << 0;
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 300e7bf..52960a9 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -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
@@ -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..d09642a 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -34,6 +34,7 @@
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.util.DisplayMetrics;
+import android.view.Gravity;
 import android.view.Surface;
 
 import com.android.launcher3.CellLayout.ContainerType;
@@ -41,6 +42,7 @@
 import com.android.launcher3.icons.DotRenderer;
 import com.android.launcher3.icons.GraphicsUtils;
 import com.android.launcher3.icons.IconNormalizer;
+import com.android.launcher3.touch.PortraitPagedViewHandler;
 import com.android.launcher3.uioverrides.ApiWrapper;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.Info;
@@ -107,15 +109,14 @@
     public Rect cellLayoutPaddingPx = new Rect();
 
     public final int edgeMarginPx;
-    public float workspaceSpringLoadShrunkTop;
-    public float workspaceSpringLoadShrunkBottom;
-    public final int workspaceSpringLoadedBottomSpace;
+    public final float workspaceContentScale;
+    private float mWorkspaceSpringLoadShrunkTop;
+    private float mWorkspaceSpringLoadShrunkBottom;
     public final int workspaceSpringLoadedMinNextPageVisiblePx;
 
     private final int extraSpace;
     public int workspaceTopPadding;
     public int workspaceBottomPadding;
-    public int extraHotseatBottomPadding;
 
     // Workspace page indicator
     public final int workspacePageIndicatorHeight;
@@ -157,24 +158,23 @@
     public int folderChildDrawablePaddingPx;
 
     // Hotseat
-    public int hotseatBarSizeExtraSpacePx;
     public final 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 final int hotseatQsbHeight;
+    public final int hotseatQsbVisualHeight;
+    private final int hotseatQsbShadowHeight;
     public int hotseatBorderSpace;
 
-    public final float qsbBottomMarginOriginalPx;
-    public int qsbBottomMarginPx;
     public int qsbWidth; // only used when isQsbInline
 
     // All apps
@@ -204,6 +204,12 @@
     public int overviewPageSpacing;
     public int overviewRowSpacing;
     public int overviewGridSideMargin;
+    public final Rect overviewGridRect = new Rect();
+    public final Rect overviewTaskRect = new Rect();
+    public final float overviewTaskWorkspaceScale;
+    public final Point overviewGridTaskDimension = new Point();
+    public final Rect overviewModalTaskRect = new Rect();
+    public final float overviewModalTaskScale;
 
     // Widgets
     public final PointF appWidgetScale = new PointF(1.0f, 1.0f);
@@ -222,7 +228,7 @@
     // Insets
     private final Rect mInsets = new Rect();
     public final Rect workspacePadding = new Rect();
-    private final Rect mHotseatPadding = new Rect();
+    private final Rect mHotseatBarPadding = new Rect();
     // When true, nav bar is on the left side of the screen.
     private boolean mIsSeascape;
 
@@ -230,6 +236,9 @@
     public DotRenderer mDotRendererWorkSpace;
     public DotRenderer mDotRendererAllApps;
 
+    // Tasks
+    public final PointF taskDimension = new PointF();
+
     // Taskbar
     public boolean isTaskbarPresent;
     // Whether Taskbar will inset the bottom of apps by taskbarSize.
@@ -275,7 +284,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 +310,7 @@
         }
 
         edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
+        workspaceContentScale = res.getFloat(R.dimen.workspace_content_scale);
 
         desiredWorkspaceHorizontalMarginPx = getHorizontalMarginPx(inv, res);
         desiredWorkspaceHorizontalMarginOriginalPx = desiredWorkspaceHorizontalMarginPx;
@@ -352,14 +362,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]
@@ -379,17 +390,28 @@
 
         numShownAllAppsColumns =
                 isTwoPanels ? inv.numDatabaseAllAppsColumns : inv.numAllAppsColumns;
-        hotseatBarSizeExtraSpacePx = 0;
-        hotseatBarTopPaddingPx =
-                res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_top_padding);
-        if (isQsbInline) {
-            hotseatBarBottomPaddingPx = res.getDimensionPixelSize(R.dimen.inline_qsb_bottom_margin);
+
+        int hotseatBarBottomSpace = pxFromDp(inv.hotseatBarBottomSpace[mTypeIndex], mMetrics);
+        int minQsbMargin = res.getDimensionPixelSize(R.dimen.min_qsb_margin);
+        hotseatQsbSpace = pxFromDp(inv.hotseatQsbSpace[mTypeIndex], mMetrics);
+        // Have a little space between the inset and the QSB
+        if (mInsets.bottom + minQsbMargin > hotseatBarBottomSpace) {
+            int availableSpace = hotseatQsbSpace - (mInsets.bottom - hotseatBarBottomSpace);
+
+            // Only change the spaces if there is space
+            if (availableSpace > 0) {
+                // Make sure there is enough space between hotseat/QSB and QSB/navBar
+                if (availableSpace < minQsbMargin * 2) {
+                    minQsbMargin = availableSpace / 2;
+                    hotseatQsbSpace = minQsbMargin;
+                } else {
+                    hotseatQsbSpace -= minQsbMargin;
+                }
+            }
+            hotseatBarBottomSpacePx = mInsets.bottom + minQsbMargin;
+
         } else {
-            hotseatBarBottomPaddingPx = (isTallDevice ? res.getDimensionPixelSize(
-                    R.dimen.dynamic_grid_hotseat_bottom_tall_padding)
-                    : res.getDimensionPixelSize(
-                            R.dimen.dynamic_grid_hotseat_bottom_non_tall_padding))
-                    + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_padding);
+            hotseatBarBottomSpacePx = hotseatBarBottomSpace;
         }
 
         springLoadedHotseatBarTopMarginPx = res.getDimensionPixelSize(
@@ -398,13 +420,19 @@
                 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) {
+            /*
+             * 3 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)
+                    + res.getDimensionPixelSize(R.dimen.taskbar_contextual_button_margin)
+                    + 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);
@@ -414,10 +442,7 @@
         overviewTaskIconDrawableSizeGridPx =
                 res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_drawable_size_grid);
         overviewTaskThumbnailTopMarginPx = overviewTaskIconSizePx + overviewTaskMarginPx * 2;
-        // In vertical bar, use the smaller task margin for the top regardless of mode.
-        overviewActionsTopMarginPx = isVerticalBarLayout()
-                ? overviewTaskMarginPx
-                : res.getDimensionPixelSize(R.dimen.overview_actions_top_margin);
+        overviewActionsTopMarginPx = res.getDimensionPixelSize(R.dimen.overview_actions_top_margin);
         overviewPageSpacing = res.getDimensionPixelSize(R.dimen.overview_page_spacing);
         overviewActionsButtonSpacing = res.getDimensionPixelSize(
                 R.dimen.overview_actions_button_spacing);
@@ -446,42 +471,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 =
@@ -503,6 +492,104 @@
         mDotRendererWorkSpace = new DotRenderer(iconSizePx, dotPath, DEFAULT_DOT_SIZE);
         mDotRendererAllApps = iconSizePx == allAppsIconSizePx ? mDotRendererWorkSpace :
                 new DotRenderer(allAppsIconSizePx, dotPath, DEFAULT_DOT_SIZE);
+
+        // Grid and Task size calculations
+        calculateGridSize();
+        getTaskDimension();
+        calculateTaskSize(res);
+        calculateGridTaskSize();
+        calculateModalTaskSize(res);
+        overviewModalTaskScale =
+                Math.min(overviewModalTaskRect.height() / (float) overviewTaskRect.height(),
+                        overviewModalTaskRect.width() / (float) overviewTaskRect.width());
+        overviewTaskWorkspaceScale = (float) overviewTaskRect.height() / getCellLayoutHeight();
+    }
+
+    private void calculateGridSize() {
+        int topMargin = overviewTaskThumbnailTopMarginPx;
+        int bottomMargin = getOverviewActionsClaimedSpace();
+        int sideMargin = overviewGridSideMargin;
+
+        overviewGridRect.set(0, 0, widthPx, heightPx);
+        overviewGridRect.inset(Math.max(mInsets.left, sideMargin), mInsets.top + topMargin,
+                Math.max(mInsets.right, sideMargin), Math.max(mInsets.bottom, bottomMargin));
+    }
+
+    private void calculateTaskSize(Resources res) {
+        int overviewMinNextPrevSize =
+                res.getDimensionPixelSize(R.dimen.overview_minimum_next_prev_size);
+        float overviewMaxScale = res.getFloat(R.dimen.overview_max_scale);
+        Rect containerRect = new Rect();
+        if (isTablet) {
+            containerRect.set(overviewGridRect);
+        } else {
+            int taskMargin = overviewTaskMarginPx;
+            containerRect.set(0, 0, widthPx, heightPx);
+            containerRect.inset(mInsets.left, mInsets.top, mInsets.right, mInsets.bottom);
+            int minimumHorizontalPadding = overviewMinNextPrevSize + taskMargin;
+            containerRect.inset(minimumHorizontalPadding, overviewTaskThumbnailTopMarginPx,
+                    minimumHorizontalPadding, getOverviewActionsClaimedSpace());
+        }
+        float scale = Math.min(
+                containerRect.width() / taskDimension.x,
+                containerRect.height() / taskDimension.y);
+        scale = Math.min(scale, overviewMaxScale);
+        int outWidth = Math.round(scale * taskDimension.x);
+        int outHeight = Math.round(scale * taskDimension.y);
+        Gravity.apply(Gravity.CENTER, outWidth, outHeight, containerRect, overviewTaskRect);
+    }
+
+    private void calculateGridTaskSize() {
+        float rowHeight =
+                (overviewTaskRect.height() + overviewTaskThumbnailTopMarginPx - overviewRowSpacing)
+                        / 2f;
+
+        float scale = (rowHeight - overviewTaskThumbnailTopMarginPx) / taskDimension.y;
+        overviewGridTaskDimension.set(
+                Math.round(scale * taskDimension.x), Math.round(scale * taskDimension.y));
+    }
+
+    /**
+     * Returns a Rect the size of a grid task with the correct positioning within the screen.
+     *
+     * @param isRecentsRtl is true when device is in LTR, false when in RTL, as grid tasks are only
+     *                     supported on tablets, which use PortraitPagedViewHandler.
+     */
+    public Rect getOverviewGridTaskRect(boolean isRecentsRtl) {
+        Rect outRect = new Rect();
+        int gravity = Gravity.TOP;
+        gravity |= isRecentsRtl ? Gravity.RIGHT : Gravity.LEFT;
+        Gravity.apply(gravity, overviewGridTaskDimension.x, overviewGridTaskDimension.y,
+                overviewTaskRect, outRect);
+        return outRect;
+    }
+
+    private void calculateModalTaskSize(Resources res) {
+        float overviewModalMaxScale = res.getFloat(R.dimen.overview_modal_max_scale);
+        Rect potentialTaskRect = new Rect(0, 0, widthPx, heightPx);
+        potentialTaskRect.inset(mInsets.left, mInsets.top, mInsets.right, mInsets.bottom);
+        int minimumHorizontalPadding = Math.round(
+                (availableWidthPx - overviewTaskRect.width() * overviewModalMaxScale) / 2);
+        potentialTaskRect.inset(
+                minimumHorizontalPadding,
+                overviewTaskMarginPx,
+                minimumHorizontalPadding,
+                heightPx - overviewTaskRect.bottom - mInsets.bottom);
+        float scale = Math.min(
+                potentialTaskRect.width() / taskDimension.x,
+                potentialTaskRect.height() / taskDimension.y);
+        int outWidth = Math.round(scale * taskDimension.x);
+        int outHeight = Math.round(scale * taskDimension.y);
+        Gravity.apply(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM, outWidth, outHeight,
+                potentialTaskRect, overviewModalTaskRect);
+    }
+
+    private void getTaskDimension() {
+        float taskHeight = heightPx;
+        if (isTablet) {
+            taskHeight -= taskbarSize;
+        }
+        taskDimension.set(widthPx, taskHeight);
     }
 
     /**
@@ -537,22 +624,27 @@
                 : res.getDimensionPixelSize(R.dimen.dynamic_grid_left_right_margin);
     }
 
-    private void updateHotseatIconSize(int hotseatIconSizePx) {
+    /** Updates hotseatCellHeightPx and hotseatBarSizePx */
+    private void updateHotseatSizes(int hotseatIconSizePx) {
         // Ensure there is enough space for folder icons, which have a slightly larger radius.
         hotseatCellHeightPx = (int) Math.ceil(hotseatIconSizePx * ICON_OVERLAP_FACTOR);
+
         if (isVerticalBarLayout()) {
             hotseatBarSizePx = hotseatIconSizePx + hotseatBarSidePaddingStartPx
                     + hotseatBarSidePaddingEndPx;
+        } else if (isQsbInline) {
+            hotseatBarSizePx = Math.max(hotseatIconSizePx, hotseatQsbVisualHeight)
+                    + hotseatBarBottomSpacePx;
         } else {
-            hotseatBarSizePx = hotseatIconSizePx + hotseatBarTopPaddingPx
-                    + hotseatBarBottomPaddingPx + (isScalableGrid ? 0 : hotseatExtraVerticalSize)
-                    + hotseatBarSizeExtraSpacePx;
+            hotseatBarSizePx = hotseatIconSizePx
+                    + hotseatQsbSpace
+                    + hotseatQsbVisualHeight
+                    + hotseatBarBottomSpacePx;
         }
     }
 
     private Point getCellLayoutBorderSpace(InvariantDeviceProfile idp) {
         return getCellLayoutBorderSpace(idp, 1f);
-
     }
 
     private Point getCellLayoutBorderSpace(InvariantDeviceProfile idp, float scale) {
@@ -764,7 +856,7 @@
         // All apps
         updateAllAppsIconSize(scale, res);
 
-        updateHotseatIconSize(iconSizePx);
+        updateHotseatSizes(iconSizePx);
 
         // Folder icon
         folderIconSizePx = IconNormalizer.getNormalizedCircleSize(iconSizePx);
@@ -802,13 +894,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 +908,7 @@
             allAppsIconTextSizePx = (int) (pxFromSp(invIconTextSizeSp, mMetrics) * scale);
             allAppsIconDrawablePaddingPx =
                     res.getDimensionPixelSize(R.dimen.all_apps_icon_drawable_padding);
+            allAppsCellWidthPx = allAppsIconSizePx + (2 * allAppsIconDrawablePaddingPx);
         }
 
         updateAllAppsContainerWidth(res);
@@ -932,35 +1025,32 @@
      * Gets the space in px from the bottom of last item in the vertical-bar hotseat to the
      * bottom of the screen.
      */
-    public int getVerticalHotseatLastItemBottomOffset() {
+    private int getVerticalHotseatLastItemBottomOffset() {
         int cellHeight = calculateCellHeight(
-                heightPx - mHotseatPadding.top - mHotseatPadding.bottom, hotseatBorderSpace,
+                heightPx - mHotseatBarPadding.top - mHotseatBarPadding.bottom, hotseatBorderSpace,
                 numShownHotseatIcons);
-        int hotseatSize = (cellHeight * numShownHotseatIcons)
-                + (hotseatBorderSpace * (numShownHotseatIcons - 1));
-        int extraHotseatEndSpacing = (heightPx - hotseatSize) / 2;
         int extraIconEndSpacing = (cellHeight - iconSizePx) / 2;
-        return extraHotseatEndSpacing + extraIconEndSpacing + mHotseatPadding.bottom;
+        return extraIconEndSpacing + mHotseatBarPadding.bottom;
     }
 
     /**
      * Gets the scaled top of the workspace in px for the spring-loaded edit state.
      */
     public float getCellLayoutSpringLoadShrunkTop() {
-        workspaceSpringLoadShrunkTop = mInsets.top + dropTargetBarTopMarginPx + dropTargetBarSizePx
+        mWorkspaceSpringLoadShrunkTop = mInsets.top + dropTargetBarTopMarginPx + dropTargetBarSizePx
                 + dropTargetBarBottomMarginPx;
-        return workspaceSpringLoadShrunkTop;
+        return mWorkspaceSpringLoadShrunkTop;
     }
 
     /**
      * Gets the scaled bottom of the workspace in px for the spring-loaded edit state.
      */
-    private float getCellLayoutSpringLoadShrunkBottom() {
+    public float getCellLayoutSpringLoadShrunkBottom() {
         int topOfHotseat = hotseatBarSizePx + springLoadedHotseatBarTopMarginPx;
-        workspaceSpringLoadShrunkBottom =
+        mWorkspaceSpringLoadShrunkBottom =
                 heightPx - (isVerticalBarLayout() ? getVerticalHotseatLastItemBottomOffset()
                         : topOfHotseat);
-        return workspaceSpringLoadShrunkBottom;
+        return mWorkspaceSpringLoadShrunkBottom;
     }
 
     /**
@@ -1022,10 +1112,11 @@
                 padding.right = hotseatBarSizePx;
             }
         } else {
-            // Pad the bottom of the workspace with search/hotseat bar sizes
-            int hotseatTop = hotseatBarSizePx;
-            int paddingBottom = hotseatTop + workspacePageIndicatorHeight
-                    + workspaceBottomPadding - mWorkspacePageIndicatorOverlapWorkspace;
+            // Pad the bottom of the workspace with hotseat bar
+            // and leave a bit of space in case a widget go all the way down
+            int paddingBottom = hotseatBarSizePx + workspaceBottomPadding
+                    + workspacePageIndicatorHeight - mWorkspacePageIndicatorOverlapWorkspace
+                    - mInsets.bottom;
             int paddingTop = workspaceTopPadding + (isScalableGrid ? 0 : edgeMarginPx);
             int paddingSide = desiredWorkspaceHorizontalMarginPx;
 
@@ -1065,50 +1156,49 @@
                     + diffOverlapFactor), 0);
 
             if (isSeascape()) {
-                mHotseatPadding.set(mInsets.left + hotseatBarSidePaddingStartPx, paddingTop,
+                mHotseatBarPadding.set(mInsets.left + hotseatBarSidePaddingStartPx, paddingTop,
                         hotseatBarSidePaddingEndPx, paddingBottom);
             } else {
-                mHotseatPadding.set(hotseatBarSidePaddingEndPx, paddingTop,
+                mHotseatBarPadding.set(hotseatBarSidePaddingEndPx, paddingTop,
                         mInsets.right + hotseatBarSidePaddingStartPx, paddingBottom);
             }
         } else if (isTaskbarPresent) {
             // Center the QSB vertically with hotseat
-            int hotseatBottomPadding = getHotseatBottomPadding();
-            int hotseatTopPadding =
-                    workspacePadding.bottom - hotseatBottomPadding - hotseatCellHeightPx;
+            int hotseatBarBottomPadding = getHotseatBarBottomPadding();
+            int hotseatBarTopPadding =
+                    hotseatBarSizePx - hotseatBarBottomPadding - hotseatCellHeightPx;
 
             // Push icons to the side
             int additionalQsbSpace = isQsbInline ? qsbWidth + hotseatBorderSpace : 0;
             int requiredWidth = iconSizePx * numShownHotseatIcons
                     + hotseatBorderSpace * (numShownHotseatIcons - 1)
                     + additionalQsbSpace;
-            int endOffset = ApiWrapper.getHotseatEndOffset(context);
-            int hotseatWidth = Math.min(requiredWidth, availableWidthPx - endOffset);
+            int hotseatWidth = Math.min(requiredWidth, availableWidthPx - hotseatBarEndOffset);
             int sideSpacing = (availableWidthPx - hotseatWidth) / 2;
 
-            mHotseatPadding.set(sideSpacing, hotseatTopPadding, sideSpacing, hotseatBottomPadding);
+            mHotseatBarPadding.set(sideSpacing, hotseatBarTopPadding, sideSpacing,
+                    hotseatBarBottomPadding);
 
             boolean isRtl = Utilities.isRtl(context.getResources());
             if (isRtl) {
-                mHotseatPadding.right += additionalQsbSpace;
+                mHotseatBarPadding.right += additionalQsbSpace;
             } else {
-                mHotseatPadding.left += additionalQsbSpace;
+                mHotseatBarPadding.left += additionalQsbSpace;
             }
 
-            if (endOffset > sideSpacing) {
+            if (hotseatBarEndOffset > sideSpacing) {
                 int diff = isRtl
-                        ? sideSpacing - endOffset
-                        : endOffset - sideSpacing;
-                mHotseatPadding.left -= diff;
-                mHotseatPadding.right += diff;
+                        ? sideSpacing - hotseatBarEndOffset
+                        : hotseatBarEndOffset - sideSpacing;
+                mHotseatBarPadding.left -= diff;
+                mHotseatBarPadding.right += diff;
             }
         } else if (isScalableGrid) {
             int sideSpacing = (availableWidthPx - qsbWidth) / 2;
-            mHotseatPadding.set(sideSpacing,
-                    hotseatBarTopPaddingPx,
+            mHotseatBarPadding.set(sideSpacing,
+                    0,
                     sideSpacing,
-                    hotseatBarSizePx - hotseatCellHeightPx - hotseatBarTopPaddingPx
-                            + mInsets.bottom);
+                    getHotseatBarBottomPadding());
         } else {
             // We want the edges of the hotseat to line up with the edges of the workspace, but the
             // icons in the hotseat are a different size, and so don't line up perfectly. To account
@@ -1117,14 +1207,15 @@
             float workspaceCellWidth = (float) widthPx / inv.numColumns;
             float hotseatCellWidth = (float) widthPx / numShownHotseatIcons;
             int hotseatAdjustment = Math.round((workspaceCellWidth - hotseatCellWidth) / 2);
-            mHotseatPadding.set(hotseatAdjustment + workspacePadding.left + cellLayoutPaddingPx.left
-                            + mInsets.left, hotseatBarTopPaddingPx,
+            mHotseatBarPadding.set(
+                    hotseatAdjustment + workspacePadding.left + cellLayoutPaddingPx.left
+                            + mInsets.left,
+                    0,
                     hotseatAdjustment + workspacePadding.right + cellLayoutPaddingPx.right
                             + mInsets.right,
-                    hotseatBarSizePx - hotseatCellHeightPx - hotseatBarTopPaddingPx
-                            + mInsets.bottom);
+                    getHotseatBarBottomPadding());
         }
-        return mHotseatPadding;
+        return mHotseatBarPadding;
     }
 
     /**
@@ -1132,27 +1223,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 +1249,7 @@
         int taskbarIconBottomSpace = (taskbarSize - iconSizePx) / 2;
         int launcherIconBottomSpace =
                 Math.min((hotseatCellHeightPx - iconSizePx) / 2, gridVisualizationPaddingY);
-        return getHotseatBottomPadding() + launcherIconBottomSpace - taskbarIconBottomSpace;
+        return getHotseatBarBottomPadding() + launcherIconBottomSpace - taskbarIconBottomSpace;
     }
 
     /**
@@ -1172,7 +1258,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;
@@ -1267,7 +1353,9 @@
         return "\t" + name + ": " + value + "px (" + dpiFromPx(value, mMetrics.densityDpi) + "dp)";
     }
 
-    public void dump(String prefix, PrintWriter writer) {
+    // LINT.IfChange
+    /** 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");
 
@@ -1313,9 +1401,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));
 
@@ -1346,26 +1437,34 @@
                 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("allAppsLeftRightPadding", allAppsLeftRightPadding));
         writer.println(prefix + pxToDpStr("allAppsLeftRightMargin", allAppsLeftRightMargin));
 
         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));
+        writer.println(prefix + pxToDpStr("mHotseatBarPadding.top", mHotseatBarPadding.top));
+        writer.println(
+                prefix + pxToDpStr("mHotseatBarPadding.bottom", mHotseatBarPadding.bottom));
+        writer.println(
+                prefix + pxToDpStr("mHotseatBarPadding.left", mHotseatBarPadding.left));
+        writer.println(
+                prefix + pxToDpStr("mHotseatBarPadding.right", mHotseatBarPadding.right));
         writer.println(prefix + "\tnumShownHotseatIcons: " + numShownHotseatIcons);
         writer.println(prefix + pxToDpStr("hotseatBorderSpace", hotseatBorderSpace));
         writer.println(prefix + "\tisQsbInline: " + isQsbInline);
@@ -1380,12 +1479,14 @@
         writer.println(prefix + pxToDpStr("workspacePadding.left", workspacePadding.left));
         writer.println(prefix + pxToDpStr("workspacePadding.top", workspacePadding.top));
         writer.println(prefix + pxToDpStr("workspacePadding.right", workspacePadding.right));
-        writer.println(prefix + pxToDpStr("workspacePadding.bottom", workspacePadding.bottom));
+        writer.println(
+                prefix + pxToDpStr("workspacePadding.bottom", workspacePadding.bottom));
 
         writer.println(prefix + pxToDpStr("iconScale", iconScale));
         writer.println(prefix + pxToDpStr("cellScaleToFit ", cellScaleToFit));
         writer.println(prefix + pxToDpStr("extraSpace", extraSpace));
-        writer.println(prefix + pxToDpStr("unscaled extraSpace", extraSpace / iconScale));
+        writer.println(
+                prefix + pxToDpStr("unscaled extraSpace", extraSpace / iconScale));
 
         if (inv.devicePaddings != null) {
             int unscaledExtraSpace = (int) (extraSpace / iconScale);
@@ -1394,10 +1495,10 @@
         }
         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("overviewTaskMarginGridPx", overviewTaskMarginGridPx));
         writer.println(prefix + pxToDpStr("overviewTaskIconSizePx", overviewTaskIconSizePx));
         writer.println(prefix + pxToDpStr("overviewTaskIconDrawableSizePx",
                 overviewTaskIconDrawableSizePx));
@@ -1415,22 +1516,78 @@
         writer.println(prefix + pxToDpStr("overviewRowSpacing", overviewRowSpacing));
         writer.println(prefix + pxToDpStr("overviewGridSideMargin", overviewGridSideMargin));
 
-        writer.println(prefix + pxToDpStr("dropTargetBarTopMarginPx", dropTargetBarTopMarginPx));
+        writer.println(
+                prefix + pxToDpStr("dropTargetBarTopMarginPx", dropTargetBarTopMarginPx));
         writer.println(prefix + pxToDpStr("dropTargetBarSizePx", dropTargetBarSizePx));
-        writer.println(
-                prefix + pxToDpStr("dropTargetBarBottomMarginPx", dropTargetBarBottomMarginPx));
+        writer.println(prefix
+                + pxToDpStr("dropTargetBarBottomMarginPx", dropTargetBarBottomMarginPx));
 
-        writer.println(
-                prefix + pxToDpStr("workspaceSpringLoadShrunkTop", workspaceSpringLoadShrunkTop));
+        writer.println(prefix
+                + pxToDpStr("workspaceSpringLoadShrunkTop", mWorkspaceSpringLoadShrunkTop));
         writer.println(prefix + pxToDpStr("workspaceSpringLoadShrunkBottom",
-                workspaceSpringLoadShrunkBottom));
-        writer.println(prefix + pxToDpStr("workspaceSpringLoadedBottomSpace",
-                workspaceSpringLoadedBottomSpace));
+                mWorkspaceSpringLoadShrunkBottom));
         writer.println(prefix + pxToDpStr("workspaceSpringLoadedMinNextPageVisiblePx",
                 workspaceSpringLoadedMinNextPageVisiblePx));
+        writer.println(prefix
+                + pxToDpStr("getWorkspaceSpringLoadScale()", getWorkspaceSpringLoadScale()));
+        writer.println(prefix + pxToDpStr("getCellLayoutHeight()", getCellLayoutHeight()));
+        writer.println(prefix + pxToDpStr("getCellLayoutWidth()", getCellLayoutWidth()));
+
+        writer.println(prefix + pxToDpStr("overviewGridRect.left", overviewGridRect.left));
+        writer.println(prefix + pxToDpStr("overviewGridRect.top", overviewGridRect.top));
+        writer.println(prefix + pxToDpStr("overviewGridRect.right", overviewGridRect.right));
+        writer.println(prefix
+                + pxToDpStr("overviewGridRect.bottom", overviewGridRect.bottom));
+        writer.println(prefix + pxToDpStr("taskDimension.x", taskDimension.x));
+        writer.println(prefix + pxToDpStr("taskDimension.y", taskDimension.y));
+        writer.println(prefix + pxToDpStr("overviewTaskRect.left", overviewTaskRect.left));
+        writer.println(prefix + pxToDpStr("overviewTaskRect.top", overviewTaskRect.top));
+        writer.println(prefix + pxToDpStr("overviewTaskRect.right", overviewTaskRect.right));
+        writer.println(prefix
+                + pxToDpStr("overviewTaskRect.bottom", overviewTaskRect.bottom));
+        writer.println(prefix
+                + pxToDpStr("overviewGridTaskDimension.x", overviewGridTaskDimension.x));
+        writer.println(prefix
+                + pxToDpStr("overviewGridTaskDimension.y", overviewGridTaskDimension.y));
+        writer.println(prefix
+                + pxToDpStr("overviewModalTaskRect.left", overviewModalTaskRect.left));
+        writer.println(prefix
+                + pxToDpStr("overviewModalTaskRect.top", overviewModalTaskRect.top));
+        writer.println(prefix
+                + pxToDpStr("overviewModalTaskRect.right", overviewModalTaskRect.right));
+        writer.println(prefix
+                + pxToDpStr("overviewModalTaskRect.bottom", overviewModalTaskRect.bottom));
+        boolean isRecentsRtl =
+                PortraitPagedViewHandler.PORTRAIT.getRecentsRtlSetting(context.getResources());
+        writer.println(prefix
+                + pxToDpStr("getOverviewGridTaskRect(" + isRecentsRtl + ").left",
+                getOverviewGridTaskRect(isRecentsRtl).left));
+        writer.println(prefix
+                + pxToDpStr("getOverviewGridTaskRect(" + isRecentsRtl + ").top",
+                getOverviewGridTaskRect(isRecentsRtl).top));
+        writer.println(prefix
+                + pxToDpStr("getOverviewGridTaskRect(" + isRecentsRtl + ").right",
+                getOverviewGridTaskRect(isRecentsRtl).right));
+        writer.println(prefix
+                + pxToDpStr("getOverviewGridTaskRect(" + isRecentsRtl + ").bottom",
+                getOverviewGridTaskRect(isRecentsRtl).bottom));
         writer.println(
-                prefix + pxToDpStr("getWorkspaceSpringLoadScale()", getWorkspaceSpringLoadScale()));
+                prefix + pxToDpStr("overviewTaskWorkspaceScale", overviewTaskWorkspaceScale));
+        writer.println(prefix + pxToDpStr("overviewModalTaskScale", overviewModalTaskScale));
     }
+    // LINT.ThenChange(
+    //     packages/apps/Launcher3/quickstep/tests/src/com/android/quickstep/DeviceProfilePhoneTest.kt,
+    //     packages/apps/Launcher3/quickstep/tests/src/com/android/quickstep/DeviceProfileVerticalBarTest.kt,
+    //     packages/apps/Launcher3/quickstep/tests/src/com/android/quickstep/DeviceProfilePhone3ButtonTest.kt,
+    //     packages/apps/Launcher3/quickstep/tests/src/com/android/quickstep/DeviceProfileVerticalBar3ButtonTest.kt,
+    //     packages/apps/Launcher3/quickstep/tests/src/com/android/quickstep/DeviceProfileTabletLandscapeTest.kt,
+    //     packages/apps/Launcher3/quickstep/tests/src/com/android/quickstep/DeviceProfileTabletPortraitTest.kt,
+    //     packages/apps/Launcher3/quickstep/tests/src/com/android/quickstep/DeviceProfileTabletLandscape3ButtonTest.kt,
+    //     packages/apps/Launcher3/quickstep/tests/src/com/android/quickstep/DeviceProfileTabletPortrait3ButtonTest.kt,
+    //     packages/apps/Launcher3/quickstep/tests/src/com/android/quickstep/DeviceProfileTwoPanelLandscapeTest.kt,
+    //     packages/apps/Launcher3/quickstep/tests/src/com/android/quickstep/DeviceProfileTwoPanelPortraitTest.kt,
+    //     packages/apps/Launcher3/quickstep/tests/src/com/android/quickstep/DeviceProfileTwoPanelLandscape3ButtonTest.kt,
+    //     packages/apps/Launcher3/quickstep/tests/src/com/android/quickstep/DeviceProfileTwoPanelPortrait3ButtonTest.kt)
 
     private static Context getContext(Context c, Info info, int orientation, WindowBounds bounds) {
         Configuration config = new Configuration(c.getResources().getConfiguration());
diff --git a/src/com/android/launcher3/DropTargetBar.java b/src/com/android/launcher3/DropTargetBar.java
index d908440..c1304d4 100644
--- a/src/com/android/launcher3/DropTargetBar.java
+++ b/src/com/android/launcher3/DropTargetBar.java
@@ -194,9 +194,10 @@
             int widthSpec = MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST);
             firstButton.measure(widthSpec, heightSpec);
             if (!mIsVertical) {
-                // Remove icons and put the button's text on two lines if text is truncated.
+                // Remove both icons and put the button's text on two lines if text is truncated.
                 if (firstButton.isTextTruncated(availableWidth)) {
                     firstButton.setIconVisible(false);
+                    secondButton.setIconVisible(false);
                     firstButton.setTextMultiLine(true);
                     firstButton.setPadding(horizontalPadding, verticalPadding / 2,
                             horizontalPadding, verticalPadding / 2);
@@ -209,8 +210,10 @@
             }
             secondButton.measure(widthSpec, heightSpec);
             if (!mIsVertical) {
+                // Remove both icons and put the button's text on two lines if text is truncated.
                 if (secondButton.isTextTruncated(availableWidth)) {
                     secondButton.setIconVisible(false);
+                    firstButton.setIconVisible(false);
                     secondButton.setTextMultiLine(true);
                     secondButton.setPadding(horizontalPadding, verticalPadding / 2,
                             horizontalPadding, verticalPadding / 2);
diff --git a/src/com/android/launcher3/FastScrollRecyclerView.java b/src/com/android/launcher3/FastScrollRecyclerView.java
index f117069..94903f2 100644
--- a/src/com/android/launcher3/FastScrollRecyclerView.java
+++ b/src/com/android/launcher3/FastScrollRecyclerView.java
@@ -24,6 +24,7 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 
 import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.launcher3.compat.AccessibilityManagerCompat;
@@ -86,15 +87,20 @@
      * Returns the available scroll height:
      *   AvailableScrollHeight = Total height of the all items - last page height
      */
-    protected abstract int getAvailableScrollHeight();
+    protected int getAvailableScrollHeight() {
+        // AvailableScrollHeight = Total height of the all items - first page height
+        int firstPageHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
+        int totalHeightOfAllItems = getItemsHeight(/* untilIndex= */ getAdapter().getItemCount());
+        int availableScrollHeight = totalHeightOfAllItems - firstPageHeight;
+        return Math.max(0, availableScrollHeight);
+    }
 
     /**
      * Returns the available scroll bar height:
      *   AvailableScrollBarHeight = Total height of the visible view - thumb height
      */
     protected int getAvailableScrollBarHeight() {
-        int availableScrollBarHeight = getScrollbarTrackHeight() - mScrollbar.getThumbHeight();
-        return availableScrollBarHeight;
+        return getScrollbarTrackHeight() - mScrollbar.getThumbHeight();
     }
 
     /**
@@ -152,12 +158,51 @@
     }
 
     /**
-     * Maps the touch (from 0..1) to the adapter position that should be visible.
-     * <p>Override in each subclass of this base class.
-     *
      * @return the scroll top of this recycler view.
      */
-    public abstract int getCurrentScrollY();
+    public int getCurrentScrollY() {
+        Adapter adapter = getAdapter();
+        if (adapter == null) {
+            return -1;
+        }
+        if (adapter.getItemCount() == 0 || getChildCount() == 0) {
+            return -1;
+        }
+
+        int itemPosition = NO_POSITION;
+        View child = null;
+
+        LayoutManager layoutManager = getLayoutManager();
+        if (layoutManager instanceof LinearLayoutManager) {
+            // Use the LayoutManager as the source of truth for visible positions. During
+            // animations, the view group child may not correspond to the visible views that appear
+            // at the top.
+            itemPosition = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();
+            child = layoutManager.findViewByPosition(itemPosition);
+        }
+
+        if (child == null) {
+            // If the layout manager returns null for any reason, which can happen before layout
+            // has occurred for the position, then look at the child of this view as a ViewGroup.
+            child = getChildAt(0);
+            itemPosition = getChildAdapterPosition(child);
+        }
+        if (itemPosition == NO_POSITION) {
+            return -1;
+        }
+        return getPaddingTop() + getItemsHeight(itemPosition)
+                - layoutManager.getDecoratedTop(child);
+    }
+
+    /**
+     * Returns the sum of the height, in pixels, of this list adapter's items from index
+     * 0 (inclusive) until {@code untilIndex} (exclusive). If untilIndex is same as the itemCount,
+     * it returns the full height of all the items.
+     *
+     * <p>If the untilIndex is larger than the total number of items in this adapter, returns the
+     * sum of all items' height.
+     */
+    protected abstract int getItemsHeight(int untilIndex);
 
     /**
      * Maps the touch (from 0..1) to the adapter position that should be visible.
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 76106fc..05ed319 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -111,9 +111,7 @@
             mQsb.setVisibility(View.VISIBLE);
             lp.gravity = Gravity.BOTTOM;
             lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
-            lp.height = grid.isTaskbarPresent
-                    ? grid.workspacePadding.bottom
-                    : grid.hotseatBarSizePx + insets.bottom;
+            lp.height = grid.hotseatBarSizePx;
         }
 
         Rect padding = grid.getHotseatLayoutPadding(getContext());
@@ -206,6 +204,13 @@
         getShortcutsAndWidgets().setAlpha(alpha);
     }
 
+    /**
+     * Sets the alpha value of just our QSB.
+     */
+    public void setQsbAlpha(float alpha) {
+        mQsb.setAlpha(alpha);
+    }
+
     public float getIconsAlpha() {
         return getShortcutsAndWidgets().getAlpha();
     }
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index db43b44..2085b84 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -46,6 +46,7 @@
 import androidx.annotation.IntDef;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
+import androidx.core.content.res.ResourcesCompat;
 
 import com.android.launcher3.model.DeviceGridState;
 import com.android.launcher3.provider.RestoreDbTask;
@@ -61,8 +62,6 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.StringWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -79,7 +78,8 @@
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({TYPE_PHONE, TYPE_MULTI_DISPLAY, TYPE_TABLET})
-    public @interface DeviceType{}
+    public @interface DeviceType {}
+
     public static final int TYPE_PHONE = 0;
     public static final int TYPE_MULTI_DISPLAY = 1;
     public static final int TYPE_TABLET = 2;
@@ -155,6 +155,8 @@
     public int numDatabaseHotseatIcons;
 
     public int[] hotseatColumnSpan;
+    public float[] hotseatBarBottomSpace;
+    public float[] hotseatQsbSpace;
 
     /**
      * Number of columns in the all apps list.
@@ -234,7 +236,8 @@
                         /*allowDisabledGrid=*/false),
                 defaultDeviceType);
 
-        Info myInfo = new Info(context, display);
+        Context displayContext = context.createDisplayContext(display);
+        Info myInfo = new Info(displayContext);
         @DeviceType int deviceType = getDeviceType(myInfo);
         DisplayOption myDisplayOption = invDistWeightedInterpolate(
                 myInfo,
@@ -361,6 +364,8 @@
                 ? closestProfile.numDatabaseHotseatIcons : closestProfile.numHotseatIcons;
         hotseatColumnSpan = closestProfile.hotseatColumnSpan;
         hotseatBorderSpaces = displayOption.hotseatBorderSpaces;
+        hotseatBarBottomSpace = displayOption.hotseatBarBottomSpace;
+        hotseatQsbSpace = displayOption.hotseatQsbSpace;
 
         numAllAppsColumns = closestProfile.numAllAppsColumns;
         numDatabaseAllAppsColumns = deviceType == TYPE_MULTI_DISPLAY
@@ -633,18 +638,6 @@
         float screenHeight = config.screenHeightDp * res.getDisplayMetrics().density;
         int rotation = WindowManagerProxy.INSTANCE.get(context).getRotation(context);
 
-        if (Utilities.IS_DEBUG_DEVICE) {
-            StringWriter stringWriter = new StringWriter();
-            PrintWriter printWriter = new PrintWriter(stringWriter);
-            DisplayController.INSTANCE.get(context).dump(printWriter);
-            printWriter.flush();
-            Log.d("b/231312158", "getDeviceProfile -"
-                            + "\nconfig: " + config
-                            + "\ndisplayMetrics: " + res.getDisplayMetrics()
-                            + "\nrotation: " + rotation
-                            + "\n" + stringWriter.toString(),
-                    new Exception());
-        }
         return getBestMatch(screenWidth, screenHeight, rotation);
     }
 
@@ -739,6 +732,7 @@
         private final int numHotseatIcons;
         private final int numShrunkenHotseatIcons;
         private final int numDatabaseHotseatIcons;
+
         private final int[] hotseatColumnSpan = new int[COUNT_SIZES];
 
         private final String dbFile;
@@ -779,6 +773,7 @@
                     R.styleable.GridDisplayOption_numShrunkenHotseatIcons, numHotseatIcons / 2);
             numDatabaseHotseatIcons = a.getInt(
                     R.styleable.GridDisplayOption_numExtendedHotseatIcons, 2 * numHotseatIcons);
+
             hotseatColumnSpan[INDEX_DEFAULT] = a.getInt(
                     R.styleable.GridDisplayOption_hotseatColumnSpan, numColumns);
             hotseatColumnSpan[INDEX_LANDSCAPE] = a.getInt(
@@ -838,6 +833,8 @@
         private final float[] horizontalMargin = new float[COUNT_SIZES];
         //TODO(http://b/228998082) remove this when 3 button spaces are fixed
         private final float[] hotseatBorderSpaces = new float[COUNT_SIZES];
+        private final float[] hotseatBarBottomSpace = new float[COUNT_SIZES];
+        private final float[] hotseatQsbSpace = new float[COUNT_SIZES];
 
         private final float[] iconSizes = new float[COUNT_SIZES];
         private final float[] textSizes = new float[COUNT_SIZES];
@@ -1063,6 +1060,34 @@
                     R.styleable.ProfileDisplayOption_hotseatBorderSpaceTwoPanelPortrait,
                     hotseatBorderSpaces[INDEX_DEFAULT]);
 
+            hotseatBarBottomSpace[INDEX_DEFAULT] = a.getFloat(
+                    R.styleable.ProfileDisplayOption_hotseatBarBottomSpace,
+                    ResourcesCompat.getFloat(context.getResources(),
+                            R.dimen.hotseat_bar_bottom_space_default));
+            hotseatBarBottomSpace[INDEX_LANDSCAPE] = a.getFloat(
+                    R.styleable.ProfileDisplayOption_hotseatBarBottomSpaceLandscape,
+                    hotseatBarBottomSpace[INDEX_DEFAULT]);
+            hotseatBarBottomSpace[INDEX_TWO_PANEL_LANDSCAPE] = a.getFloat(
+                    R.styleable.ProfileDisplayOption_hotseatBarBottomSpaceTwoPanelLandscape,
+                    hotseatBarBottomSpace[INDEX_DEFAULT]);
+            hotseatBarBottomSpace[INDEX_TWO_PANEL_PORTRAIT] = a.getFloat(
+                    R.styleable.ProfileDisplayOption_hotseatBarBottomSpaceTwoPanelPortrait,
+                    hotseatBarBottomSpace[INDEX_DEFAULT]);
+
+            hotseatQsbSpace[INDEX_DEFAULT] = a.getFloat(
+                    R.styleable.ProfileDisplayOption_hotseatQsbSpace,
+                    ResourcesCompat.getFloat(context.getResources(),
+                            R.dimen.hotseat_qsb_space_default));
+            hotseatQsbSpace[INDEX_LANDSCAPE] = a.getFloat(
+                    R.styleable.ProfileDisplayOption_hotseatQsbSpaceLandscape,
+                    hotseatQsbSpace[INDEX_DEFAULT]);
+            hotseatQsbSpace[INDEX_TWO_PANEL_LANDSCAPE] = a.getFloat(
+                    R.styleable.ProfileDisplayOption_hotseatQsbSpaceTwoPanelLandscape,
+                    hotseatQsbSpace[INDEX_DEFAULT]);
+            hotseatQsbSpace[INDEX_TWO_PANEL_PORTRAIT] = a.getFloat(
+                    R.styleable.ProfileDisplayOption_hotseatQsbSpaceTwoPanelPortrait,
+                    hotseatQsbSpace[INDEX_DEFAULT]);
+
             a.recycle();
         }
 
@@ -1098,6 +1123,8 @@
                 minCellSize[i].y *= w;
                 horizontalMargin[i] *= w;
                 hotseatBorderSpaces[i] *= w;
+                hotseatBarBottomSpace[i] *= w;
+                hotseatQsbSpace[i] *= w;
                 allAppsCellSize[i].x *= w;
                 allAppsCellSize[i].y *= w;
                 allAppsIconSizes[i] *= w;
@@ -1121,6 +1148,8 @@
                 minCellSize[i].y += p.minCellSize[i].y;
                 horizontalMargin[i] += p.horizontalMargin[i];
                 hotseatBorderSpaces[i] += p.hotseatBorderSpaces[i];
+                hotseatBarBottomSpace[i] += p.hotseatBarBottomSpace[i];
+                hotseatQsbSpace[i] += p.hotseatQsbSpace[i];
                 allAppsCellSize[i].x += p.allAppsCellSize[i].x;
                 allAppsCellSize[i].y += p.allAppsCellSize[i].y;
                 allAppsIconSizes[i] += p.allAppsIconSizes[i];
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index ebed31b..528613c 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -30,10 +30,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;
@@ -97,6 +99,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;
@@ -195,6 +198,7 @@
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.util.TraceHelper;
 import com.android.launcher3.util.UiThreadHelper;
+import com.android.launcher3.util.ViewCapture;
 import com.android.launcher3.util.ViewOnDrawExecutor;
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.views.FloatingIconView;
@@ -266,7 +270,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 +282,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 +306,17 @@
     public static final int DISPLAY_WORKSPACE_TRACE_COOKIE = 0;
     public static final int DISPLAY_ALL_APPS_TRACE_COOKIE = 1;
 
+    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);
+
     private Configuration mOldConfig;
 
     @Thunk
     Workspace<?> mWorkspace;
     @Thunk
     DragLayer mDragLayer;
-    private DragController mDragController;
 
     private WidgetManagerHelper mAppWidgetManager;
     private LauncherAppWidgetHost mAppWidgetHost;
@@ -369,6 +380,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;
@@ -386,6 +398,7 @@
     private LauncherState mPrevLauncherState;
 
     private StringCache mStringCache;
+    private ViewCapture mViewCapture;
 
     @Override
     @TargetApi(Build.VERSION_CODES.S)
@@ -465,7 +478,7 @@
         mIconCache = app.getIconCache();
         mAccessibilityDelegate = createAccessibilityDelegate();
 
-        mDragController = new LauncherDragController(this);
+        initDragController();
         mAllAppsController = new AllAppsTransitionController(this);
         mStateManager = new StateManager<>(this, NORMAL);
 
@@ -614,6 +627,13 @@
         super.onConfigurationChanged(newConfig);
     }
 
+    /**
+     * Initializes the drag controller.
+     */
+    protected void initDragController() {
+        mDragController = new LauncherDragController(this);
+    }
+
     @Override
     public void onIdpChanged(boolean modelPropertiesChanged) {
         initDeviceProfile(mDeviceProfile.inv);
@@ -740,7 +760,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 +1100,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
@@ -1473,6 +1489,14 @@
     public void onAttachedToWindow() {
         super.onAttachedToWindow();
         mOverlayManager.onAttachedToWindow();
+        if (FeatureFlags.CONTINUOUS_VIEW_TREE_CAPTURE.get()) {
+            View root = getDragLayer().getRootView();
+            if (mViewCapture != null) {
+                root.getViewTreeObserver().removeOnDrawListener(mViewCapture);
+            }
+            mViewCapture = new ViewCapture(root);
+            root.getViewTreeObserver().addOnDrawListener(mViewCapture);
+        }
     }
 
     @Override
@@ -2992,11 +3016,15 @@
         writer.println(prefix + "\tmRotationHelper: " + mRotationHelper);
         writer.println(prefix + "\tmAppWidgetHost.isListening: " + mAppWidgetHost.isListening());
 
+        if (mViewCapture != null) {
+            writer.println(prefix + "\tmViewCapture: " + mViewCapture.dumpToString());
+        }
+
         // Extra logging for general debugging
         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);
@@ -3220,7 +3248,12 @@
      * @param progress Transition progress from 0 to 1; where 0 => home and 1 => widgets.
      */
     public void onWidgetsTransition(float progress) {
-        // No-Op
+        if (mDeviceProfile.isTablet) {
+            float scale =
+                    Utilities.comp(Utilities.comp(mDeviceProfile.workspaceContentScale) * progress);
+            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/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 5aa8a46..a20ff8c 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -162,7 +162,7 @@
     private synchronized boolean prepForMigration(String dbFile, String targetTableName,
             Supplier<DatabaseHelper> src, Supplier<DatabaseHelper> dst) {
         if (TextUtils.equals(dbFile, mOpenHelper.getDatabaseName())) {
-            Log.e("b/198965093", "prepForMigration - target db is same as current: " + dbFile);
+            Log.e(TAG, "prepForMigration - target db is same as current: " + dbFile);
             return false;
         }
 
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index cba0b7d..73be5be 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -1187,9 +1187,7 @@
     }
 
     public int getScrollForPage(int index) {
-        // TODO(b/233112195): Use !pageScrollsInitialized() instead of mPageScrolls == null, once we
-        // root cause where we should be using runOnPageScrollsInitialized().
-        if (mPageScrolls == null || index >= mPageScrolls.length || index < 0) {
+        if (!pageScrollsInitialized() || index >= mPageScrolls.length || index < 0) {
             return 0;
         } else {
             return mPageScrolls[index];
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 7b96838..dc8c739 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -587,15 +587,6 @@
     }
 
     /**
-     * Returns an intent for starting the default home activity
-     */
-    public static Intent createHomeIntent() {
-        return new Intent(Intent.ACTION_MAIN)
-                .addCategory(Intent.CATEGORY_HOME)
-                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-    }
-
-    /**
      * Wraps a message with a TTS span, so that a different message is spoken than
      * what is getting displayed.
      * @param msg original message
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 4903d77..e6dea8f 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -159,7 +159,7 @@
     private LayoutTransition mLayoutTransition;
     @Thunk final WallpaperManager mWallpaperManager;
 
-    private ShortcutAndWidgetContainer mDragSourceInternal;
+    protected ShortcutAndWidgetContainer mDragSourceInternal;
 
     @Thunk final IntSparseArrayMap<CellLayout> mWorkspaceScreens = new IntSparseArrayMap<>();
     @Thunk final IntArray mScreenOrder = new IntArray();
@@ -195,7 +195,7 @@
     @Thunk final Launcher mLauncher;
     @Thunk DragController mDragController;
 
-    private final int[] mTempXY = new int[2];
+    protected final int[] mTempXY = new int[2];
     private final float[] mTempFXY = new float[2];
     private final Rect mTempRect = new Rect();
     @Thunk float[] mDragViewVisualCenter = new float[2];
@@ -3418,7 +3418,11 @@
         return getPageDescription(page);
     }
 
-    private String getPageDescription(int page) {
+    /**
+     * @param page page index.
+     * @return Description of the page at the given page index.
+     */
+    public String getPageDescription(int page) {
         int nScreens = getChildCount();
         int extraScreenId = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
         if (extraScreenId >= 0 && nScreens > 1) {
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index af17cf7..de34416 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -31,7 +31,6 @@
 import android.util.Log;
 import android.util.SparseIntArray;
 import android.view.MotionEvent;
-import android.view.View;
 
 import androidx.recyclerview.widget.RecyclerView;
 
@@ -342,24 +341,7 @@
     }
 
     @Override
-    public int getCurrentScrollY() {
-        // Return early if there are no items or we haven't been measured
-        List<AllAppsGridAdapter.AdapterItem> items = mApps.getAdapterItems();
-        if (items.isEmpty() || mNumAppsPerRow == 0 || getChildCount() == 0) {
-            return -1;
-        }
-
-        // Calculate the y and offset for the item
-        View child = getChildAt(0);
-        int position = getChildAdapterPosition(child);
-        if (position == NO_POSITION) {
-            return -1;
-        }
-        return getPaddingTop() +
-                getCurrentScrollY(position, getLayoutManager().getDecoratedTop(child));
-    }
-
-    public int getCurrentScrollY(int position, int offset) {
+    protected int getItemsHeight(int position) {
         List<AllAppsGridAdapter.AdapterItem> items = mApps.getAdapterItems();
         AllAppsGridAdapter.AdapterItem posItem = position < items.size()
                 ? items.get(position) : null;
@@ -400,17 +382,7 @@
             }
             mCachedScrollPositions.put(position, y);
         }
-        return y - offset;
-    }
-
-    /**
-     * Returns the available scroll height:
-     * AvailableScrollHeight = Total height of the all items - last page height
-     */
-    @Override
-    protected int getAvailableScrollHeight() {
-        return getPaddingTop() + getCurrentScrollY(getAdapter().getItemCount(), 0)
-                - getHeight() + getPaddingBottom();
+        return y;
     }
 
     public int getScrollBarTop() {
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index a4a2085..a86faab 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -23,7 +23,6 @@
 import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
-import static com.android.launcher3.util.SystemUiController.UI_STATE_ALLAPPS;
 
 import android.animation.Animator;
 import android.animation.Animator.AnimatorListener;
@@ -37,7 +36,6 @@
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorListeners;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.anim.PropertySetter;
@@ -46,7 +44,6 @@
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.util.MultiAdditivePropertyFactory;
 import com.android.launcher3.util.MultiValueAlpha;
-import com.android.launcher3.util.UiThreadHelper;
 import com.android.launcher3.views.ScrimView;
 
 /**
@@ -231,7 +228,6 @@
     public void setStateWithAnimation(LauncherState toState,
             StateAnimationConfig config, PendingAnimation builder) {
         if (NORMAL.equals(toState) && mLauncher.isInState(ALL_APPS)) {
-            UiThreadHelper.hideKeyboardAsync(mLauncher, mLauncher.getAppsView().getWindowToken());
             builder.addEndListener(success -> {
                 // Reset pull back progress and alpha after switching states.
                 ALL_APPS_PULL_BACK_TRANSLATION.set(this, 0f);
@@ -293,11 +289,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);
@@ -318,6 +309,7 @@
         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..8bbe216 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -50,7 +50,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 +92,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);
@@ -260,9 +260,9 @@
             }
         } 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..459fa88 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
@@ -63,6 +63,11 @@
     // 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_WORK_EDU_CARD = 1 << 5;
+    public static final int VIEW_TYPE_WORK_DISABLED_CARD = 1 << 6;
+
+    public static final int NEXT_ID = 7;
+
     // Common view type masks
     public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_ALL_APPS_DIVIDER;
     public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON;
@@ -225,6 +230,12 @@
             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) {
@@ -258,8 +269,12 @@
                 }
                 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..f3c5dd6 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
@@ -147,8 +147,7 @@
 
         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 +252,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 +303,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;
         }
@@ -602,6 +602,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);
@@ -799,12 +801,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/FloatingHeaderRow.java b/src/com/android/launcher3/allapps/FloatingHeaderRow.java
index 6ff2132..75e527a 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderRow.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderRow.java
@@ -15,11 +15,8 @@
  */
 package com.android.launcher3.allapps;
 
-import android.graphics.Rect;
 import android.view.View;
 
-import com.android.launcher3.DeviceProfile;
-
 /**
  * A abstract representation of a row in all-apps view
  */
@@ -29,8 +26,6 @@
 
     void setup(FloatingHeaderView parent, FloatingHeaderRow[] allRows, boolean tabsHidden);
 
-    void setInsets(Rect insets, DeviceProfile grid);
-
     int getExpectedHeight();
 
     /**
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index 6ecbad2..c5bdb69 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -17,6 +17,7 @@
 
 import android.animation.ValueAnimator;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.util.ArrayMap;
@@ -30,7 +31,6 @@
 import androidx.annotation.Nullable;
 import androidx.recyclerview.widget.RecyclerView;
 
-import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.BaseAllAppsContainerView.AdapterHolder;
@@ -84,7 +84,7 @@
     // These two values are necessary to ensure that the header protection is drawn correctly.
     private final int mHeaderTopAdjustment;
     private final int mHeaderBottomAdjustment;
-    private final boolean mHeaderProtectionSupported;
+    private boolean mHeaderProtectionSupported;
 
     protected ViewGroup mTabLayout;
     private AllAppsRecyclerView mMainRV;
@@ -122,9 +122,14 @@
         mHeaderBottomAdjustment = context.getResources()
                 .getDimensionPixelSize(R.dimen.all_apps_header_bottom_adjustment);
         mHeaderProtectionSupported = context.getResources().getBoolean(
-                R.bool.config_header_protection_supported)
-                // TODO(b/208599118) Support header protection for bottom sheet.
-                && !ActivityContext.lookupContext(context).getDeviceProfile().isTablet;
+                R.bool.config_header_protection_supported);
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        mHeaderProtectionSupported = getContext().getResources().getBoolean(
+                R.bool.config_header_protection_supported);
     }
 
     @Override
@@ -158,22 +163,6 @@
         PluginManagerWrapper.INSTANCE.get(getContext()).removePluginListener(this);
     }
 
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        mTabLayout.getLayoutParams().width = getTabWidth();
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-    }
-
-    /**
-     * Returns distance between left and right app icons
-     */
-    public int getTabWidth() {
-        DeviceProfile grid = ActivityContext.lookupContext(getContext()).getDeviceProfile();
-        int totalWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
-        int iconPadding = totalWidth / grid.numShownAllAppsColumns - grid.allAppsIconSizePx;
-        return totalWidth - iconPadding - grid.allAppsIconDrawablePaddingPx;
-    }
-
     private void recreateAllRowsArray() {
         int pluginCount = mPluginRows.size();
         if (pluginCount == 0) {
@@ -423,15 +412,6 @@
         p.y = getTop() - mCurrentRV.getTop() - ((ViewGroup) mCurrentRV.getParent()).getTop();
     }
 
-    public boolean hasVisibleContent() {
-        for (FloatingHeaderRow row : mAllRows) {
-            if (row.hasVisibleContent()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     public boolean isHeaderProtectionSupported() {
         return mHeaderProtectionSupported;
     }
@@ -443,10 +423,9 @@
 
     @Override
     public void setInsets(Rect insets) {
-        DeviceProfile grid = ActivityContext.lookupContext(getContext()).getDeviceProfile();
-        for (FloatingHeaderRow row : mAllRows) {
-            row.setInsets(insets, grid);
-        }
+        int leftRightPadding = ActivityContext.lookupContext(getContext())
+                .getDeviceProfile().allAppsLeftRightPadding;
+        setPadding(leftRightPadding, getPaddingTop(), leftRightPadding, getPaddingBottom());
     }
 
     public <T extends FloatingHeaderRow> T findFixedRowByType(Class<T> type) {
diff --git a/src/com/android/launcher3/allapps/PluginHeaderRow.java b/src/com/android/launcher3/allapps/PluginHeaderRow.java
index 5b5fbb7..a9d36d1 100644
--- a/src/com/android/launcher3/allapps/PluginHeaderRow.java
+++ b/src/com/android/launcher3/allapps/PluginHeaderRow.java
@@ -18,10 +18,8 @@
 import static android.view.View.INVISIBLE;
 import static android.view.View.VISIBLE;
 
-import android.graphics.Rect;
 import android.view.View;
 
-import com.android.launcher3.DeviceProfile;
 import com.android.systemui.plugins.AllAppsRow;
 
 /**
@@ -43,9 +41,6 @@
             boolean tabsHidden) { }
 
     @Override
-    public void setInsets(Rect insets, DeviceProfile grid) { }
-
-    @Override
     public int getExpectedHeight() {
         return mPlugin.getExpectedHeight();
     }
diff --git a/src/com/android/launcher3/allapps/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 836cd5a..968a556 100644
--- a/src/com/android/launcher3/allapps/WorkEduCard.java
+++ b/src/com/android/launcher3/allapps/WorkEduCard.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.allapps;
 
+import static com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.getTabWidth;
+
 import android.content.Context;
 import android.util.AttributeSet;
 import android.view.View;
@@ -22,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;
 
 /**
@@ -70,14 +74,18 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         findViewById(R.id.action_btn).setOnClickListener(this);
-        MarginLayoutParams lp = ((MarginLayoutParams) findViewById(R.id.wrapper).getLayoutParams());
-        lp.width = mActivityContext.getAppsView().getFloatingHeaderView().getTabWidth();
+
+        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();
     }
 
@@ -87,14 +95,10 @@
     }
 
     @Override
-    public void onAnimationRepeat(Animation animation) {
-
-    }
+    public void onAnimationRepeat(Animation animation) { }
 
     @Override
-    public void onAnimationStart(Animation animation) {
-
-    }
+    public void onAnimationStart(Animation animation) { }
 
     private void removeCard() {
         if (mPosition == -1) {
@@ -107,8 +111,14 @@
         }
     }
 
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int size = MeasureSpec.getSize(widthMeasureSpec);
+        findViewById(R.id.wrapper).getLayoutParams().width = getTabWidth(getContext(), size);
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+    }
+
     public void setPosition(int position) {
         mPosition = position;
     }
-
 }
diff --git a/src/com/android/launcher3/allapps/WorkModeSwitch.java b/src/com/android/launcher3/allapps/WorkModeSwitch.java
index 733577e..aee7c4c 100644
--- a/src/com/android/launcher3/allapps/WorkModeSwitch.java
+++ b/src/com/android/launcher3/allapps/WorkModeSwitch.java
@@ -16,20 +16,23 @@
 package com.android.launcher3.allapps;
 
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TURN_OFF_WORK_APPS_TAP;
+import static com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.getTabWidth;
 
 import android.content.Context;
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.View;
-import android.view.ViewGroup;
+import android.view.ViewGroup.MarginLayoutParams;
 import android.view.WindowInsets;
 import android.widget.Button;
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Insettable;
+import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.KeyboardInsetAnimationCallback;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.StringCache;
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip;
@@ -50,7 +53,6 @@
     private boolean mWorkEnabled;
     private boolean mOnWorkTab;
 
-
     public WorkModeSwitch(Context context) {
         this(context, null, 0);
     }
@@ -85,15 +87,39 @@
 
     @Override
     public void setInsets(Rect insets) {
-        int bottomInset = insets.bottom - mInsets.bottom;
         mInsets.set(insets);
-        ViewGroup.MarginLayoutParams marginLayoutParams =
-                (ViewGroup.MarginLayoutParams) getLayoutParams();
-        if (marginLayoutParams != null) {
-            marginLayoutParams.bottomMargin = bottomInset + marginLayoutParams.bottomMargin;
+        MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
+        if (lp != null) {
+            int bottomMargin = getResources().getDimensionPixelSize(R.dimen.work_fab_margin_bottom);
+            if (FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get()) {
+                bottomMargin <<= 1;  // Double margin to add space above search bar.
+                bottomMargin += getResources().getDimensionPixelSize(R.dimen.qsb_widget_height);
+            }
+
+            DeviceProfile dp = ActivityContext.lookupContext(getContext()).getDeviceProfile();
+            if (!dp.isGestureMode) {
+                if (dp.isTaskbarPresent) {
+                    bottomMargin += dp.taskbarSize;
+                } else {
+                    bottomMargin += insets.bottom;
+                }
+            }
+
+            lp.bottomMargin = bottomMargin;
         }
     }
 
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        DeviceProfile dp = ActivityContext.lookupContext(getContext()).getDeviceProfile();
+        View parent = (View) getParent();
+        int size = parent.getWidth() - parent.getPaddingLeft() - parent.getPaddingRight()
+                - 2 * dp.allAppsLeftRightPadding;
+        int tabWidth = getTabWidth(getContext(), size);
+        int shift = (size - tabWidth) / 2 + dp.allAppsLeftRightPadding;
+        setTranslationX(Utilities.isRtl(getResources()) ? shift : -shift);
+    }
 
     @Override
     public void onActivePageChanged(int page) {
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 b70cb13..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;
@@ -26,20 +28,19 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Log;
-import android.view.ViewGroup;
 
 import androidx.annotation.IntDef;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 
-import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
-import com.android.launcher3.config.FeatureFlags;
+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;
 
 /**
@@ -50,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
      */
@@ -66,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();
     }
 
@@ -122,7 +119,6 @@
 
     private void updateCurrentState(@WorkProfileState int currentState) {
         mCurrentState = currentState;
-        mAdapterProvider.updateCurrentState(currentState);
         if (getAH() != null) {
             getAH().mAppsList.updateAdapterItems();
         }
@@ -144,24 +140,6 @@
             mWorkModeSwitch = (WorkModeSwitch) mAllApps.getLayoutInflater().inflate(
                     R.layout.work_mode_fab, mAllApps, false);
         }
-        ViewGroup.MarginLayoutParams lp =
-                (ViewGroup.MarginLayoutParams) mWorkModeSwitch.getLayoutParams();
-        int workFabMarginBottom =
-                mWorkModeSwitch.getResources().getDimensionPixelSize(
-                        R.dimen.work_fab_margin_bottom);
-        if (FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get()) {
-            workFabMarginBottom <<= 1;  // Double margin to add space above search bar.
-            workFabMarginBottom +=
-                    mWorkModeSwitch.getResources().getDimensionPixelSize(R.dimen.qsb_widget_height);
-        }
-        if (!mAllApps.mActivityContext.getDeviceProfile().isGestureMode){
-            workFabMarginBottom += mAllApps.mActivityContext.getDeviceProfile().getInsets().bottom;
-        }
-        lp.bottomMargin = workFabMarginBottom;
-        int totalScreenWidth = mDeviceProfile.widthPx;
-        int personalWorkTabWidth =
-                mAllApps.mActivityContext.getAppsView().getFloatingHeaderView().getTabWidth();
-        lp.rightMargin = lp.leftMargin = (totalScreenWidth - personalWorkTabWidth) / 2;
         if (mWorkModeSwitch.getParent() != mAllApps) {
             mAllApps.addView(mWorkModeSwitch);
         }
@@ -181,10 +159,6 @@
         mWorkModeSwitch = null;
     }
 
-    public WorkAdapterProvider getAdapterProvider() {
-        return mAdapterProvider;
-    }
-
     public Predicate<ItemInfo> getMatcher() {
         return mMatcher;
     }
@@ -201,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/anim/AnimatedPropertySetter.java b/src/com/android/launcher3/anim/AnimatedPropertySetter.java
index e5f5e7c..82e645a 100644
--- a/src/com/android/launcher3/anim/AnimatedPropertySetter.java
+++ b/src/com/android/launcher3/anim/AnimatedPropertySetter.java
@@ -43,9 +43,17 @@
 
     @Override
     public Animator setViewAlpha(View view, float alpha, TimeInterpolator interpolator) {
-        if (view == null || view.getAlpha() == alpha) {
+        if (view == null) {
             return NO_OP;
         }
+
+        // Short-circuit if the view already has this alpha value, but make sure the visibility is
+        // set correctly for the requested alpha.
+        if (Float.compare(view.getAlpha(), alpha) == 0) {
+            AlphaUpdateListener.updateVisibility(view);
+            return NO_OP;
+        }
+
         ObjectAnimator anim = ObjectAnimator.ofFloat(view, View.ALPHA, alpha);
         anim.addListener(new AlphaUpdateListener(view));
         anim.setInterpolator(interpolator);
@@ -89,6 +97,18 @@
         return anim;
     }
 
+    @NonNull
+    @Override
+    public <T> Animator setColor(T target, IntProperty<T> property, int value,
+            TimeInterpolator interpolator) {
+        if (property.get(target) == value) {
+            return NO_OP;
+        }
+        Animator anim = ObjectAnimator.ofArgb(target, property, value);
+        anim.setInterpolator(interpolator);
+        add(anim);
+        return anim;
+    }
 
     /**
      * Adds a callback to be run on every frame of the animation
diff --git a/src/com/android/launcher3/anim/PropertySetter.java b/src/com/android/launcher3/anim/PropertySetter.java
index d2207f6..b0ed2d2 100644
--- a/src/com/android/launcher3/anim/PropertySetter.java
+++ b/src/com/android/launcher3/anim/PropertySetter.java
@@ -89,6 +89,16 @@
     }
 
     /**
+     * Updates a color property of the target using the provided interpolator
+     */
+    @NonNull
+    public <T> Animator setColor(T target, IntProperty<T> property, int value,
+            TimeInterpolator interpolator) {
+        property.setValue(target, value);
+        return NO_OP;
+    }
+
+    /**
      * Runs the animation as part of setting the property
      */
     public abstract void add(Animator animatorSet);
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 33beacd..4fd13b2 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -96,6 +96,9 @@
     public static final BooleanFlag ENABLE_QUICK_SEARCH = new DeviceFlag("ENABLE_QUICK_SEARCH",
             true, "Use quick search behavior.");
 
+    public static final BooleanFlag ENABLE_HIDE_HEADER = new DeviceFlag("ENABLE_HIDE_HEADER",
+            true, "Hide header on keyboard before typing in all apps");
+
     public static final BooleanFlag COLLECT_SEARCH_HISTORY = new DeviceFlag(
             "COLLECT_SEARCH_HISTORY", false, "Allow launcher to collect search history for log");
 
@@ -209,9 +212,6 @@
             "ENABLE_SCRIM_FOR_APP_LAUNCH", false,
             "Enables scrim during app launch animation.");
 
-    public static final BooleanFlag ENABLE_SPLIT_SELECT = getDebugFlag(
-            "ENABLE_SPLIT_SELECT", true, "Uses new split screen selection overview UI");
-
     public static final BooleanFlag ENABLE_ENFORCED_ROUNDED_CORNERS = new DeviceFlag(
             "ENABLE_ENFORCED_ROUNDED_CORNERS", true, "Enforce rounded corners on all App Widgets");
 
@@ -277,6 +277,17 @@
             "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 void initialize(Context context) {
         synchronized (sDebugFlags) {
             for (DebugFlag flag : sDebugFlags) {
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 0264ae2..09fe740 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -363,7 +363,10 @@
             // If the content is already removed, ignore
             return;
         }
-        View newContent = getViewFromDrawable(getContext(), crossFadeDrawable);
+        ImageView newContent = getViewFromDrawable(getContext(), crossFadeDrawable);
+        // We need to fill the ImageView with the content, otherwise the shapes of the final view
+        // and the drag view might not match exactly
+        newContent.setScaleType(ImageView.ScaleType.FIT_XY);
         newContent.measure(makeMeasureSpec(mWidth, EXACTLY), makeMeasureSpec(mHeight, EXACTLY));
         newContent.layout(0, 0, mWidth, mHeight);
         addViewInLayout(newContent, 0, new LayoutParams(mWidth, mHeight));
@@ -573,7 +576,7 @@
         }
     }
 
-    private static View getViewFromDrawable(Context context, Drawable drawable) {
+    private static ImageView getViewFromDrawable(Context context, Drawable drawable) {
         ImageView iv = new ImageView(context);
         iv.setImageDrawable(drawable);
         return iv;
diff --git a/src/com/android/launcher3/folder/PreviewItemManager.java b/src/com/android/launcher3/folder/PreviewItemManager.java
index 6355b62..2e5f2e5 100644
--- a/src/com/android/launcher3/folder/PreviewItemManager.java
+++ b/src/com/android/launcher3/folder/PreviewItemManager.java
@@ -79,6 +79,8 @@
     private int mPrevTopPadding = -1;
     private Drawable mReferenceDrawable = null;
 
+    private int mNumOfPrevItems = 0;
+
     // These hold the first page preview items
     private ArrayList<PreviewItemDrawingParams> mFirstPageParams = new ArrayList<>();
     // These hold the current page preview items. It is empty if the current page is the first page.
@@ -254,7 +256,6 @@
 
     void buildParamsForPage(int page, ArrayList<PreviewItemDrawingParams> params, boolean animate) {
         List<WorkspaceItemInfo> items = mIcon.getPreviewItemsOnPage(page);
-        int prevNumItems = params.size();
 
         // We adjust the size of the list to match the number of items in the preview.
         while (items.size() < params.size()) {
@@ -278,8 +279,9 @@
                     mReferenceDrawable = p.drawable;
                 }
             } else {
-                FolderPreviewItemAnim anim = new FolderPreviewItemAnim(this, p, i, prevNumItems, i,
-                        numItemsInFirstPagePreview, DROP_IN_ANIMATION_DURATION, null);
+                FolderPreviewItemAnim anim = new FolderPreviewItemAnim(this, p, i,
+                        mNumOfPrevItems, i, numItemsInFirstPagePreview, DROP_IN_ANIMATION_DURATION,
+                        null);
 
                 if (p.anim != null) {
                     if (p.anim.hasEqualFinalState(anim)) {
@@ -318,7 +320,9 @@
     }
 
     void updatePreviewItems(boolean animate) {
+        int numOfPrevItemsAux = mFirstPageParams.size();
         buildParamsForPage(0, mFirstPageParams, animate);
+        mNumOfPrevItems = numOfPrevItemsAux;
     }
 
     void updatePreviewItems(Predicate<WorkspaceItemInfo> itemCheck) {
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index d5bcb0c..a8546e8 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -43,6 +43,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 +55,8 @@
 import android.view.WindowManager;
 import android.widget.TextClock;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
@@ -177,10 +181,12 @@
     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());
@@ -224,6 +230,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,
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/model/DeviceGridState.java b/src/com/android/launcher3/model/DeviceGridState.java
index 35fcb78..46f0b0b 100644
--- a/src/com/android/launcher3/model/DeviceGridState.java
+++ b/src/com/android/launcher3/model/DeviceGridState.java
@@ -134,10 +134,13 @@
      * DeviceGridState without migration, or false otherwise.
      */
     public boolean isCompatible(DeviceGridState other) {
-        if (this == other) return true;
-        if (other == null) return false;
-        return mNumHotseat == other.mNumHotseat
-                && Objects.equals(mGridSizeString, other.mGridSizeString);
+        if (this == other) {
+            return true;
+        }
+        if (other == null) {
+            return false;
+        }
+        return Objects.equals(mDbFile, other.mDbFile);
     }
 
     public Integer getColumns() {
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
index ef9250c..c25929a 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
@@ -56,6 +56,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * This class takes care of shrinking the workspace (by maximum of one row and one column), as a
@@ -109,9 +110,8 @@
     private static boolean needsToMigrate(
             DeviceGridState srcDeviceState, DeviceGridState destDeviceState) {
         boolean needsToMigrate = !destDeviceState.isCompatible(srcDeviceState);
-        // TODO(b/198965093): Revert this change after bug is fixed
         if (needsToMigrate) {
-            Log.d("b/198965093", "Migration is needed. destDeviceState: " + destDeviceState
+            Log.i(TAG, "Migration is needed. destDeviceState: " + destDeviceState
                     + ", srcDeviceState: " + srcDeviceState);
         }
         return needsToMigrate;
@@ -267,38 +267,33 @@
 
     /** Return what's in the src but not in the dest */
     private static List<DbEntry> calcDiff(List<DbEntry> src, List<DbEntry> dest) {
-        Set<String> destIntentSet = new HashSet<>();
-        Set<Map<String, Integer>> destFolderIntentSet = new HashSet<>();
+        Map<String, Integer> destIdSet = new HashMap<>();
         for (DbEntry entry : dest) {
-            if (entry.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
-                destFolderIntentSet.add(getFolderIntents(entry));
+            String entryID = entry.getEntryMigrationId();
+            if (destIdSet.containsKey(entryID)) {
+                destIdSet.put(entryID, destIdSet.get(entryID) + 1);
             } else {
-                destIntentSet.add(entry.mIntent);
+                destIdSet.put(entryID, 1);
             }
         }
         List<DbEntry> diff = new ArrayList<>();
         for (DbEntry entry : src) {
-            if (entry.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
-                if (!destFolderIntentSet.contains(getFolderIntents(entry))) {
+            String entryID = entry.getEntryMigrationId();
+            if (destIdSet.containsKey(entryID)) {
+                Integer count = destIdSet.get(entryID);
+                if (count <= 0) {
                     diff.add(entry);
+                    destIdSet.remove(entryID);
+                } else {
+                    destIdSet.put(entryID, count - 1);
                 }
             } else {
-                if (!destIntentSet.contains(entry.mIntent)) {
-                    diff.add(entry);
-                }
+                diff.add(entry);
             }
         }
         return diff;
     }
 
-    private static Map<String, Integer> getFolderIntents(DbEntry entry) {
-        Map<String, Integer> folder = new HashMap<>();
-        for (String intent : entry.mFolderItems.keySet()) {
-            folder.put(intent, entry.mFolderItems.get(intent).size());
-        }
-        return folder;
-    }
-
     private static void insertEntryInDb(SQLiteDatabase db, Context context, DbEntry entry,
             String srcTableName, String destTableName) {
         int id = copyEntryAndUpdate(db, context, entry, srcTableName, destTableName);
@@ -780,5 +775,31 @@
             values.put(LauncherSettings.Favorites.SPANX, spanX);
             values.put(LauncherSettings.Favorites.SPANY, spanY);
         }
+
+        /**
+         * This method should return an id that should be the same for two folders containing the
+         * same elements.
+         */
+        private String getFolderMigrationId() {
+            return mFolderItems.keySet().stream()
+                    .map(intentString -> mFolderItems.get(intentString).size() + intentString)
+                    .sorted()
+                    .collect(Collectors.joining(","));
+        }
+
+        /** This id is not used in the DB is only used while doing the migration and it identifies
+         * an entry on each workspace. For example two calculator icons would have the same
+         * migration id even thought they have different database ids.
+         */
+        public String getEntryMigrationId() {
+            switch (itemType) {
+                case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+                    return getFolderMigrationId();
+                case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+                    return mProvider;
+                default:
+                    return mIntent;
+            }
+        }
     }
 }
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 489bc38..a9d272e 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -57,7 +57,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)
@@ -343,7 +345,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/pageindicators/WorkspacePageIndicator.java b/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java
index 1681ea5..87ae890 100644
--- a/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java
+++ b/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java
@@ -271,7 +271,7 @@
         } else {
             lp.leftMargin = lp.rightMargin = 0;
             lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
-            lp.bottomMargin = grid.hotseatBarSizePx + insets.bottom;
+            lp.bottomMargin = grid.hotseatBarSizePx;
         }
         setLayoutParams(lp);
     }
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 49d97d2..8e7a10c 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -72,6 +72,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.stream.Collectors;
 
 /**
@@ -244,23 +245,32 @@
                 mNotificationContainer);
     }
 
+    private void initializeSystemShortcuts(List<SystemShortcut> shortcuts) {
+        if (shortcuts.isEmpty()) {
+            return;
+        }
+        // If there is only 1 shortcut, add it to its own container so it can show text and icon
+        if (shortcuts.size() == 1) {
+            initializeSystemShortcut(R.layout.system_shortcut, this, shortcuts.get(0));
+            return;
+        }
+        mSystemShortcutContainer = inflateAndAdd(R.layout.system_shortcut_icons, this);
+        for (SystemShortcut shortcut : shortcuts) {
+            initializeSystemShortcut(
+                    R.layout.system_shortcut_icon_only, mSystemShortcutContainer,
+                    shortcut);
+        }
+    }
+
     @TargetApi(Build.VERSION_CODES.P)
     public void populateAndShow(final BubbleTextView originalIcon, int shortcutCount,
-            final List<NotificationKeyData> notificationKeys, List<SystemShortcut> systemShortcuts) {
+            final List<NotificationKeyData> notificationKeys, List<SystemShortcut> shortcuts) {
         mNumNotifications = notificationKeys.size();
         mOriginalIcon = originalIcon;
 
         boolean hasDeepShortcuts = shortcutCount > 0;
         mContainerWidth = getResources().getDimensionPixelSize(R.dimen.bg_popup_item_width);
 
-        // if there are deep shortcuts, we might want to increase the width of shortcuts to fit
-        // horizontally laid out system shortcuts.
-        if (hasDeepShortcuts) {
-            mContainerWidth = Math.max(mContainerWidth,
-                    systemShortcuts.size() * getResources()
-                            .getDimensionPixelSize(R.dimen.system_shortcut_header_icon_touch_size)
-            );
-        }
         // Add views
         if (mNumNotifications > 0) {
             // Add notification entries
@@ -279,6 +289,24 @@
             mDeepShortcutContainer = findViewById(R.id.deep_shortcuts_container);
         }
         if (hasDeepShortcuts) {
+            // Remove the widget shortcut fom the list
+            List<SystemShortcut> systemShortcuts = shortcuts
+                    .stream()
+                    .filter(shortcut -> !(shortcut instanceof SystemShortcut.Widgets))
+                    .collect(Collectors.toList());
+            Optional<SystemShortcut.Widgets> widgetShortcutOpt = shortcuts
+                    .stream()
+                    .filter(shortcut -> shortcut instanceof SystemShortcut.Widgets)
+                    .map(SystemShortcut.Widgets.class::cast)
+                    .findFirst();
+
+            // if there are deep shortcuts, we might want to increase the width of shortcuts to fit
+            // horizontally laid out system shortcuts.
+            mContainerWidth = Math.max(mContainerWidth,
+                    systemShortcuts.size() * getResources()
+                            .getDimensionPixelSize(R.dimen.system_shortcut_header_icon_touch_size)
+            );
+
             mDeepShortcutContainer.setVisibility(View.VISIBLE);
 
             for (int i = shortcutCount; i > 0; i--) {
@@ -288,30 +316,19 @@
             }
             updateHiddenShortcuts();
 
-            if (!systemShortcuts.isEmpty()) {
-                for (SystemShortcut shortcut : systemShortcuts) {
-                    if (shortcut instanceof SystemShortcut.Widgets) {
-                        if (mWidgetContainer == null) {
-                            mWidgetContainer = inflateAndAdd(R.layout.widget_shortcut_container,
-                                    this);
-                        }
-                        initializeWidgetShortcut(mWidgetContainer, shortcut);
-                    }
+            if (widgetShortcutOpt.isPresent()) {
+                if (mWidgetContainer == null) {
+                    mWidgetContainer = inflateAndAdd(R.layout.widget_shortcut_container,
+                            this);
                 }
-                mSystemShortcutContainer = inflateAndAdd(R.layout.system_shortcut_icons, this);
-
-                for (SystemShortcut shortcut : systemShortcuts) {
-                    if (!(shortcut instanceof SystemShortcut.Widgets)) {
-                        initializeSystemShortcut(
-                                R.layout.system_shortcut_icon_only, mSystemShortcutContainer,
-                                shortcut);
-                    }
-                }
+                initializeWidgetShortcut(mWidgetContainer, widgetShortcutOpt.get());
             }
+
+            initializeSystemShortcuts(systemShortcuts);
         } else {
             mDeepShortcutContainer.setVisibility(View.GONE);
-            if (!systemShortcuts.isEmpty()) {
-                for (SystemShortcut shortcut : systemShortcuts) {
+            if (!shortcuts.isEmpty()) {
+                for (SystemShortcut shortcut : shortcuts) {
                     initializeSystemShortcut(R.layout.system_shortcut, this, shortcut);
                 }
             }
diff --git a/src/com/android/launcher3/statemanager/StatefulActivity.java b/src/com/android/launcher3/statemanager/StatefulActivity.java
index c554d06..2158dea 100644
--- a/src/com/android/launcher3/statemanager/StatefulActivity.java
+++ b/src/com/android/launcher3/statemanager/StatefulActivity.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.statemanager;
 
+import static com.android.launcher3.LauncherState.FLAG_CLOSE_POPUPS;
 import static com.android.launcher3.LauncherState.FLAG_NON_INTERACTIVE;
 
 import android.os.Handler;
@@ -23,6 +24,7 @@
 
 import androidx.annotation.CallSuper;
 
+import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.LauncherRootView;
 import com.android.launcher3.Utilities;
@@ -87,6 +89,10 @@
         if (mDeferredResumePending) {
             handleDeferredResume();
         }
+
+        if (state.hasFlag(FLAG_CLOSE_POPUPS)) {
+            AbstractFloatingView.closeAllOpenViews(this, !state.hasFlag(FLAG_NON_INTERACTIVE));
+        }
     }
 
     /**
diff --git a/src/com/android/launcher3/testing/HotseatCellCenterRequest.java b/src/com/android/launcher3/testing/HotseatCellCenterRequest.java
new file mode 100644
index 0000000..cbb847e
--- /dev/null
+++ b/src/com/android/launcher3/testing/HotseatCellCenterRequest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.testing;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Request object for querying a hotseat cell region in Rect.
+ */
+public class HotseatCellCenterRequest implements TestInformationRequest {
+    public final int cellInd;
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(cellInd);
+    }
+
+    @Override
+    public String getRequestName() {
+        return TestProtocol.REQUEST_HOTSEAT_CELL_CENTER;
+    }
+
+    public static final Parcelable.Creator<HotseatCellCenterRequest> CREATOR =
+            new Parcelable.Creator<HotseatCellCenterRequest>() {
+
+                @Override
+                public HotseatCellCenterRequest createFromParcel(Parcel source) {
+                    return new HotseatCellCenterRequest(source);
+                }
+
+                @Override
+                public HotseatCellCenterRequest[] newArray(int size) {
+                    return new HotseatCellCenterRequest[size];
+                }
+            };
+
+    private HotseatCellCenterRequest(int cellInd) {
+        this.cellInd = cellInd;
+    }
+
+    private HotseatCellCenterRequest(Parcel in) {
+        this(in.readInt());
+    }
+
+    /**
+     * Create a builder for HotseatCellCenterRequest.
+     *
+     * @return HotseatCellCenterRequest builder.
+     */
+    public static HotseatCellCenterRequest.Builder builder() {
+        return new HotseatCellCenterRequest.Builder();
+    }
+
+    /**
+     * HotseatCellCenterRequest Builder.
+     */
+    public static final class Builder {
+        private int mCellInd;
+
+        private Builder() {
+            mCellInd = 0;
+        }
+
+        /**
+         * Set the index of hotseat cells.
+         */
+        public HotseatCellCenterRequest.Builder setCellInd(int i) {
+            this.mCellInd = i;
+            return this;
+        }
+
+        /**
+         * build the HotseatCellCenterRequest.
+         */
+        public HotseatCellCenterRequest build() {
+            return new HotseatCellCenterRequest(mCellInd);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index 242d2d4..0334b96 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -33,6 +33,7 @@
 
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Hotseat;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
@@ -153,10 +154,6 @@
                         mDeviceProfile.isTwoPanels);
                 return response;
 
-            case TestProtocol.REQUEST_SET_FORCE_PAUSE_TIMEOUT:
-                TestProtocol.sForcePauseTimeout = Long.parseLong(arg);
-                return response;
-
             case TestProtocol.REQUEST_GET_HAD_NONTEST_EVENTS:
                 response.putBoolean(
                         TestProtocol.TEST_INFO_RESPONSE_FIELD, TestLogging.sHadEventsNotFromTest);
@@ -185,7 +182,7 @@
                     return new int[]{cellLayout.getCountX(), cellLayout.getCountY()};
                 });
 
-            case TestProtocol.REQUEST_WORKSPACE_CELL_CENTER:
+            case TestProtocol.REQUEST_WORKSPACE_CELL_CENTER: {
                 final WorkspaceCellCenterRequest request = extra.getParcelable(
                         TestProtocol.TEST_INFO_REQUEST_FIELD);
                 return getLauncherUIProperty(Bundle::putParcelable, launcher -> {
@@ -197,6 +194,21 @@
                             cellLayout, request.cellX, request.cellY, request.spanX, request.spanY);
                     return new Point(cellRect.centerX(), cellRect.centerY());
                 });
+            }
+
+            case TestProtocol.REQUEST_HOTSEAT_CELL_CENTER: {
+                final HotseatCellCenterRequest request = extra.getParcelable(
+                        TestProtocol.TEST_INFO_REQUEST_FIELD);
+                return getLauncherUIProperty(Bundle::putParcelable, launcher -> {
+                    final Hotseat hotseat = launcher.getHotseat();
+                    final Rect cellRect = getDescendantRectRelativeToDragLayerForCell(launcher,
+                            hotseat, request.cellInd, /* cellY= */ 0,
+                            /* spanX= */ 1, /* spanY= */ 1);
+                    // TODO(b/234322284): return the real center point.
+                    return new Point(cellRect.left + (cellRect.right - cellRect.left) / 3,
+                            cellRect.top + (cellRect.bottom - cellRect.top) / 3);
+                });
+            }
 
             case TestProtocol.REQUEST_HAS_TIS: {
                 response.putBoolean(
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 3a030a8..9bc9067 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -115,6 +115,8 @@
     public static final String REQUEST_WORKSPACE_CELL_LAYOUT_SIZE = "workspace-cell-layout-size";
     public static final String REQUEST_WORKSPACE_CELL_CENTER = "workspace-cell-center";
 
+    public static final String REQUEST_HOTSEAT_CELL_CENTER = "hotseat-cell-center";
+
     public static final String REQUEST_GET_FOCUSED_TASK_HEIGHT_FOR_TABLET =
             "get-focused-task-height-for-tablet";
     public static final String REQUEST_GET_GRID_TASK_SIZE_RECT_FOR_TABLET =
@@ -122,9 +124,6 @@
     public static final String REQUEST_GET_OVERVIEW_PAGE_SPACING = "get-overview-page-spacing";
     public static final String REQUEST_ENABLE_ROTATION = "enable_rotation";
 
-    public static Long sForcePauseTimeout;
-    public static final String REQUEST_SET_FORCE_PAUSE_TIMEOUT = "set-force-pause-timeout";
-
     public static boolean sDebugTracing = false;
     public static final String REQUEST_ENABLE_DEBUG_TRACING = "enable-debug-tracing";
     public static final String REQUEST_DISABLE_DEBUG_TRACING = "disable-debug-tracing";
@@ -139,4 +138,9 @@
     public static final String MISSING_PROMISE_ICON = "b/202985412";
     public static final String BAD_STATE = "b/223498680";
     public static final String TASKBAR_IN_APP_STATE = "b/227657604";
+
+    public static final String REQUEST_EMULATE_DISPLAY = "emulate-display";
+    public static final String REQUEST_STOP_EMULATE_DISPLAY = "stop-emulate-display";
+    public static final String REQUEST_IS_EMULATE_DISPLAY_RUNNING = "is-emulate-display-running";
+    public static final String REQUEST_EMULATE_PRINT_DEVICE = "emulate-print-device";
 }
diff --git a/src/com/android/launcher3/touch/AllAppsSwipeController.java b/src/com/android/launcher3/touch/AllAppsSwipeController.java
index db43baa..37b76fb 100644
--- a/src/com/android/launcher3/touch/AllAppsSwipeController.java
+++ b/src/com/android/launcher3/touch/AllAppsSwipeController.java
@@ -47,48 +47,88 @@
  */
 public class AllAppsSwipeController extends AbstractStateChangeTouchController {
 
-    private static final float ALLAPPS_STAGGERED_FADE_THRESHOLD = 0.5f;
+    private static final float ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD = 0.8f;
+    private static final float ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD = 0.5f;
+    private static final float ALL_APPS_SCRIM_VISIBLE_THRESHOLD = 0.1f;
+    private static final float ALL_APPS_STAGGERED_FADE_THRESHOLD = 0.5f;
 
-    // Custom timing for NORMAL -> ALL_APPS on phones only.
-    private static final float WORKSPACE_MOTION_START = 0.1667f;
-    private static final float ALL_APPS_STATE_TRANSITION = 0.305f;
-    private static final float ALL_APPS_FADE_END = 0.4717f;
+    public static final Interpolator ALL_APPS_SCRIM_RESPONDER =
+            Interpolators.clampToProgress(
+                    LINEAR, ALL_APPS_SCRIM_VISIBLE_THRESHOLD, ALL_APPS_STAGGERED_FADE_THRESHOLD);
+    public static final Interpolator ALL_APPS_CLAMPING_RESPONDER =
+            Interpolators.clampToProgress(
+                    LINEAR,
+                    1 - ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD,
+                    1 - ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD);
+
+    // ---- Custom interpolators for NORMAL -> ALL_APPS on phones only. ----
+
+    private static final float WORKSPACE_MOTION_START_ATOMIC = 0.1667f;
+    private static final float ALL_APPS_STATE_TRANSITION_ATOMIC = 0.305f;
+    private static final float ALL_APPS_STATE_TRANSITION_MANUAL = 0.4f;
+    private static final float ALL_APPS_FADE_END_ATOMIC = 0.4717f;
     private static final float ALL_APPS_FULL_DEPTH_PROGRESS = 0.5f;
 
-    public static final Interpolator ALLAPPS_STAGGERED_FADE_EARLY_RESPONDER =
-            Interpolators.clampToProgress(LINEAR, 0, ALLAPPS_STAGGERED_FADE_THRESHOLD);
-    public static final Interpolator ALLAPPS_STAGGERED_FADE_LATE_RESPONDER =
-            Interpolators.clampToProgress(LINEAR, ALLAPPS_STAGGERED_FADE_THRESHOLD, 1f);
+    private static final Interpolator LINEAR_EARLY_MANUAL =
+            Interpolators.clampToProgress(LINEAR, 0f, ALL_APPS_STATE_TRANSITION_MANUAL);
+    private static final Interpolator STEP_TRANSITION_ATOMIC =
+            Interpolators.clampToProgress(FINAL_FRAME, 0f, ALL_APPS_STATE_TRANSITION_ATOMIC);
+    private static final Interpolator STEP_TRANSITION_MANUAL =
+            Interpolators.clampToProgress(FINAL_FRAME, 0f, ALL_APPS_STATE_TRANSITION_MANUAL);
 
-    // Custom interpolators for NORMAL -> ALL_APPS on phones only.
     // The blur to All Apps is set to be complete when the interpolator is at 0.5.
-    public static final Interpolator BLUR =
+    private static final Interpolator BLUR_ADJUSTED =
+            Interpolators.mapToProgress(LINEAR, 0f, ALL_APPS_FULL_DEPTH_PROGRESS);
+    public static final Interpolator BLUR_ATOMIC =
             Interpolators.clampToProgress(
-                    Interpolators.mapToProgress(
-                            LINEAR, 0f, ALL_APPS_FULL_DEPTH_PROGRESS),
-                    WORKSPACE_MOTION_START, ALL_APPS_STATE_TRANSITION);
-    public static final Interpolator WORKSPACE_FADE =
-            Interpolators.clampToProgress(FINAL_FRAME, 0f, ALL_APPS_STATE_TRANSITION);
-    public static final Interpolator WORKSPACE_SCALE =
+                    BLUR_ADJUSTED, WORKSPACE_MOTION_START_ATOMIC, ALL_APPS_STATE_TRANSITION_ATOMIC);
+    public static final Interpolator BLUR_MANUAL =
+            Interpolators.clampToProgress(BLUR_ADJUSTED, 0f, ALL_APPS_STATE_TRANSITION_MANUAL);
+
+    public static final Interpolator WORKSPACE_FADE_ATOMIC = STEP_TRANSITION_ATOMIC;
+    public static final Interpolator WORKSPACE_FADE_MANUAL = STEP_TRANSITION_MANUAL;
+
+    public static final Interpolator WORKSPACE_SCALE_ATOMIC =
             Interpolators.clampToProgress(
-                    EMPHASIZED_ACCELERATE, WORKSPACE_MOTION_START, ALL_APPS_STATE_TRANSITION);
-    public static final Interpolator HOTSEAT_FADE = WORKSPACE_FADE;
-    public static final Interpolator HOTSEAT_SCALE = HOTSEAT_FADE;
-    public static final Interpolator HOTSEAT_TRANSLATE =
+                    EMPHASIZED_ACCELERATE, WORKSPACE_MOTION_START_ATOMIC,
+                    ALL_APPS_STATE_TRANSITION_ATOMIC);
+    public static final Interpolator WORKSPACE_SCALE_MANUAL = LINEAR_EARLY_MANUAL;
+
+    public static final Interpolator HOTSEAT_FADE_ATOMIC = STEP_TRANSITION_ATOMIC;
+    public static final Interpolator HOTSEAT_FADE_MANUAL = STEP_TRANSITION_MANUAL;
+
+    public static final Interpolator HOTSEAT_SCALE_ATOMIC = STEP_TRANSITION_ATOMIC;
+    public static final Interpolator HOTSEAT_SCALE_MANUAL = LINEAR_EARLY_MANUAL;
+
+    public static final Interpolator HOTSEAT_TRANSLATE_ATOMIC =
             Interpolators.clampToProgress(
-                    EMPHASIZED_ACCELERATE, WORKSPACE_MOTION_START, ALL_APPS_STATE_TRANSITION);
-    public static final Interpolator SCRIM_FADE =
+                    EMPHASIZED_ACCELERATE, WORKSPACE_MOTION_START_ATOMIC,
+                    ALL_APPS_STATE_TRANSITION_ATOMIC);
+    public static final Interpolator HOTSEAT_TRANSLATE_MANUAL = STEP_TRANSITION_MANUAL;
+
+    public static final Interpolator SCRIM_FADE_ATOMIC =
             Interpolators.clampToProgress(
                     Interpolators.mapToProgress(LINEAR, 0f, 0.8f),
-                    WORKSPACE_MOTION_START, ALL_APPS_STATE_TRANSITION);
-    public static final Interpolator ALL_APPS_FADE =
+                    WORKSPACE_MOTION_START_ATOMIC, ALL_APPS_STATE_TRANSITION_ATOMIC);
+    public static final Interpolator SCRIM_FADE_MANUAL = LINEAR_EARLY_MANUAL;
+
+    public static final Interpolator ALL_APPS_FADE_ATOMIC =
             Interpolators.clampToProgress(
-                    Interpolators.mapToProgress(DECELERATED_EASE, 0.2f, 1.0f),
-                    ALL_APPS_STATE_TRANSITION, ALL_APPS_FADE_END);
-    public static final Interpolator ALL_APPS_VERTICAL_PROGRESS =
+                    Interpolators.mapToProgress(DECELERATED_EASE, 0.2f, 1f),
+                    ALL_APPS_STATE_TRANSITION_ATOMIC, ALL_APPS_FADE_END_ATOMIC);
+    public static final Interpolator ALL_APPS_FADE_MANUAL =
+            Interpolators.clampToProgress(LINEAR, ALL_APPS_STATE_TRANSITION_MANUAL, 1f);
+
+    public static final Interpolator ALL_APPS_VERTICAL_PROGRESS_ATOMIC =
             Interpolators.clampToProgress(
-                    Interpolators.mapToProgress(EMPHASIZED_DECELERATE, 0.4f, 1.0f),
-                    ALL_APPS_STATE_TRANSITION, 1.0f);
+                    Interpolators.mapToProgress(EMPHASIZED_DECELERATE, 0.4f, 1f),
+                    ALL_APPS_STATE_TRANSITION_ATOMIC, 1f);
+    public static final Interpolator ALL_APPS_VERTICAL_PROGRESS_MANUAL =
+            Interpolators.clampToProgress(
+                    Interpolators.mapToProgress(LINEAR, ALL_APPS_STATE_TRANSITION_MANUAL, 1f),
+                    ALL_APPS_STATE_TRANSITION_MANUAL, 1f);
+
+    // --------
 
     public AllAppsSwipeController(Launcher l) {
         super(l, SingleAxisSwipeDetector.VERTICAL);
@@ -141,6 +181,7 @@
     protected StateAnimationConfig getConfigForStates(LauncherState fromState,
             LauncherState toState) {
         StateAnimationConfig config = super.getConfigForStates(fromState, toState);
+        config.userControlled = true;
         if (fromState == NORMAL && toState == ALL_APPS) {
             applyNormalToAllAppsAnimConfig(mLauncher, config);
         } else if (fromState == ALL_APPS && toState == NORMAL) {
@@ -150,36 +191,75 @@
     }
 
     /**
-     * Applies Animation config values for transition from all apps to home
+     * Applies Animation config values for transition from all apps to home.
      */
     public static void applyAllAppsToNormalConfig(Launcher launcher, StateAnimationConfig config) {
-        boolean isTablet = launcher.getDeviceProfile().isTablet;
-        config.setInterpolator(ANIM_SCRIM_FADE, ALLAPPS_STAGGERED_FADE_LATE_RESPONDER);
-        config.setInterpolator(ANIM_ALL_APPS_FADE, isTablet
-                ? FINAL_FRAME : ALLAPPS_STAGGERED_FADE_EARLY_RESPONDER);
-        if (!isTablet) {
-            config.setInterpolator(ANIM_WORKSPACE_FADE, INSTANT);
+        if (launcher.getDeviceProfile().isTablet) {
+            config.setInterpolator(ANIM_SCRIM_FADE,
+                    Interpolators.reverse(ALL_APPS_SCRIM_RESPONDER));
+            config.setInterpolator(ANIM_ALL_APPS_FADE, FINAL_FRAME);
+            if (!config.userControlled) {
+                config.setInterpolator(ANIM_VERTICAL_PROGRESS, EMPHASIZED_ACCELERATE);
+            }
+        } else {
+            if (config.userControlled) {
+                config.setInterpolator(ANIM_DEPTH, Interpolators.reverse(BLUR_MANUAL));
+                config.setInterpolator(ANIM_WORKSPACE_FADE,
+                        Interpolators.reverse(WORKSPACE_FADE_MANUAL));
+                config.setInterpolator(ANIM_WORKSPACE_SCALE,
+                        Interpolators.reverse(WORKSPACE_SCALE_MANUAL));
+                config.setInterpolator(ANIM_HOTSEAT_FADE,
+                        Interpolators.reverse(HOTSEAT_FADE_MANUAL));
+                config.setInterpolator(ANIM_HOTSEAT_SCALE,
+                        Interpolators.reverse(HOTSEAT_SCALE_MANUAL));
+                config.setInterpolator(ANIM_HOTSEAT_TRANSLATE,
+                        Interpolators.reverse(HOTSEAT_TRANSLATE_MANUAL));
+                config.setInterpolator(ANIM_SCRIM_FADE, Interpolators.reverse(SCRIM_FADE_MANUAL));
+                config.setInterpolator(ANIM_ALL_APPS_FADE,
+                        Interpolators.reverse(ALL_APPS_FADE_MANUAL));
+                config.setInterpolator(ANIM_VERTICAL_PROGRESS,
+                        Interpolators.reverse(ALL_APPS_VERTICAL_PROGRESS_MANUAL));
+            } else {
+                config.setInterpolator(ANIM_SCRIM_FADE,
+                        Interpolators.reverse(ALL_APPS_SCRIM_RESPONDER));
+                config.setInterpolator(ANIM_ALL_APPS_FADE, ALL_APPS_CLAMPING_RESPONDER);
+                config.setInterpolator(ANIM_WORKSPACE_FADE, INSTANT);
+                config.setInterpolator(ANIM_VERTICAL_PROGRESS, EMPHASIZED_ACCELERATE);
+            }
         }
     }
 
     /**
-     * Applies Animation config values for transition from home to all apps
+     * Applies Animation config values for transition from home to all apps.
      */
-    public static void applyNormalToAllAppsAnimConfig(Launcher launcher,
-            StateAnimationConfig config) {
+    public static void applyNormalToAllAppsAnimConfig(
+            Launcher launcher, StateAnimationConfig config) {
         if (launcher.getDeviceProfile().isTablet) {
-            config.setInterpolator(ANIM_SCRIM_FADE, ALLAPPS_STAGGERED_FADE_EARLY_RESPONDER);
             config.setInterpolator(ANIM_ALL_APPS_FADE, INSTANT);
+            config.setInterpolator(ANIM_SCRIM_FADE, ALL_APPS_SCRIM_RESPONDER);
+            if (!config.userControlled) {
+                config.setInterpolator(ANIM_VERTICAL_PROGRESS, EMPHASIZED_DECELERATE);
+            }
         } else {
-            config.setInterpolator(ANIM_DEPTH, BLUR);
-            config.setInterpolator(ANIM_WORKSPACE_FADE, WORKSPACE_FADE);
-            config.setInterpolator(ANIM_WORKSPACE_SCALE, WORKSPACE_SCALE);
-            config.setInterpolator(ANIM_HOTSEAT_FADE, HOTSEAT_FADE);
-            config.setInterpolator(ANIM_HOTSEAT_SCALE, HOTSEAT_SCALE);
-            config.setInterpolator(ANIM_HOTSEAT_TRANSLATE, HOTSEAT_TRANSLATE);
-            config.setInterpolator(ANIM_SCRIM_FADE, SCRIM_FADE);
-            config.setInterpolator(ANIM_ALL_APPS_FADE, ALL_APPS_FADE);
-            config.setInterpolator(ANIM_VERTICAL_PROGRESS, ALL_APPS_VERTICAL_PROGRESS);
+            config.setInterpolator(ANIM_DEPTH, config.userControlled ? BLUR_MANUAL : BLUR_ATOMIC);
+            config.setInterpolator(ANIM_WORKSPACE_FADE,
+                    config.userControlled ? WORKSPACE_FADE_MANUAL : WORKSPACE_FADE_ATOMIC);
+            config.setInterpolator(ANIM_WORKSPACE_SCALE,
+                    config.userControlled ? WORKSPACE_SCALE_MANUAL : WORKSPACE_SCALE_ATOMIC);
+            config.setInterpolator(ANIM_HOTSEAT_FADE,
+                    config.userControlled ? HOTSEAT_FADE_MANUAL : HOTSEAT_FADE_ATOMIC);
+            config.setInterpolator(ANIM_HOTSEAT_SCALE,
+                    config.userControlled ? HOTSEAT_SCALE_MANUAL : HOTSEAT_SCALE_ATOMIC);
+            config.setInterpolator(ANIM_HOTSEAT_TRANSLATE,
+                    config.userControlled ? HOTSEAT_TRANSLATE_MANUAL : HOTSEAT_TRANSLATE_ATOMIC);
+            config.setInterpolator(ANIM_SCRIM_FADE,
+                    config.userControlled ? SCRIM_FADE_MANUAL : SCRIM_FADE_ATOMIC);
+            config.setInterpolator(ANIM_ALL_APPS_FADE,
+                    config.userControlled ? ALL_APPS_FADE_MANUAL : ALL_APPS_FADE_ATOMIC);
+            config.setInterpolator(ANIM_VERTICAL_PROGRESS,
+                    config.userControlled
+                            ? ALL_APPS_VERTICAL_PROGRESS_MANUAL
+                            : ALL_APPS_VERTICAL_PROGRESS_ATOMIC);
         }
     }
 }
diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java
index 6bae745..53cd416 100644
--- a/src/com/android/launcher3/touch/ItemLongClickListener.java
+++ b/src/com/android/launcher3/touch/ItemLongClickListener.java
@@ -38,6 +38,7 @@
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.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 +52,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;
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index b477905..fa4eb70 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -19,6 +19,7 @@
 import static android.view.Gravity.BOTTOM;
 import static android.view.Gravity.CENTER_VERTICAL;
 import static android.view.Gravity.END;
+import static android.view.Gravity.LEFT;
 import static android.view.Gravity.START;
 import static android.view.Gravity.TOP;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
@@ -50,9 +51,9 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.SplitConfigurationOptions;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
 import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
-import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds;
 import com.android.launcher3.views.BaseDragLayer;
 
 import java.util.Collections;
@@ -311,7 +312,7 @@
 
     @Override
     public Pair<Float, Float> getDwbLayoutTranslations(int taskViewWidth,
-            int taskViewHeight, StagedSplitBounds splitBounds, DeviceProfile deviceProfile,
+            int taskViewHeight, SplitBounds splitBounds, DeviceProfile deviceProfile,
             View[] thumbnailViews, int desiredTaskId, View banner) {
         boolean isRtl = banner.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
         float translationX = 0;
@@ -424,7 +425,7 @@
     }
 
     @Override
-    public void updateStagedSplitIconParams(View out, float onScreenRectCenterX,
+    public void updateSplitIconParams(View out, float onScreenRectCenterX,
             float onScreenRectCenterY, float fullscreenScaleX, float fullscreenScaleY,
             int drawableWidth, int drawableHeight, DeviceProfile dp,
             @StagePosition int stagePosition) {
@@ -436,6 +437,28 @@
     }
 
     @Override
+    public void setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight,
+            int splitInstructionsWidth, int threeButtonNavShift) {
+        out.setPivotX(0);
+        out.setPivotY(0);
+        out.setRotation(getDegreesRotated());
+        int distanceToEdge = out.getResources().getDimensionPixelSize(
+                R.dimen.split_instructions_bottom_margin_phone_landscape);
+        // Adjust for any insets on the left edge
+        int insetCorrectionX = dp.getInsets().left;
+        // Center the view in case of unbalanced insets on top or bottom of screen
+        int insetCorrectionY = (dp.getInsets().bottom - dp.getInsets().top) / 2;
+        out.setTranslationX(splitInstructionsHeight + distanceToEdge - insetCorrectionX);
+        out.setTranslationY(((splitInstructionsHeight - splitInstructionsWidth) / 2f)
+                + insetCorrectionY);
+        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) out.getLayoutParams();
+        // Setting gravity to LEFT instead of the lint-recommended START because we always want this
+        // view to be screen-left when phone is in landscape, regardless of the RtL setting.
+        lp.gravity = LEFT | CENTER_VERTICAL;
+        out.setLayoutParams(lp);
+    }
+
+    @Override
     public void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp,
             @StagePosition int stagePosition, Rect out1, Rect out2) {
         // In fake land/seascape, the window bounds are always top and bottom half
@@ -447,7 +470,7 @@
 
     @Override
     public void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect,
-            StagedSplitBounds splitInfo, int desiredStagePosition) {
+            SplitBounds splitInfo, int desiredStagePosition) {
         float topLeftTaskPercent = splitInfo.appsStackedVertically
                 ? splitInfo.topTaskPercent
                 : splitInfo.leftTaskPercent;
@@ -464,7 +487,7 @@
 
     @Override
     public void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot,
-            int parentWidth, int parentHeight, StagedSplitBounds splitBoundsConfig,
+            int parentWidth, int parentHeight, SplitBounds splitBoundsConfig,
             DeviceProfile dp, boolean isRtl) {
         int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
         int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
@@ -506,7 +529,7 @@
     public void setSplitIconParams(View primaryIconView, View secondaryIconView,
             int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight,
             int groupedTaskViewHeight, int groupedTaskViewWidth, boolean isRtl,
-            DeviceProfile deviceProfile, StagedSplitBounds splitConfig) {
+            DeviceProfile deviceProfile, SplitBounds splitConfig) {
         FrameLayout.LayoutParams primaryIconParams =
                 (FrameLayout.LayoutParams) primaryIconView.getLayoutParams();
         FrameLayout.LayoutParams secondaryIconParams =
@@ -515,7 +538,8 @@
         // We calculate the "midpoint" of the thumbnail area, and place the icons there.
         // This is the place where the thumbnail area splits by default, in a near-50/50 split.
         // It is usually not exactly 50/50, due to insets/screen cutouts.
-        int fullscreenInsetThickness = deviceProfile.getInsets().top;
+        int fullscreenInsetThickness = deviceProfile.getInsets().top
+                - deviceProfile.getInsets().bottom;
         int fullscreenMidpointFromBottom = ((deviceProfile.heightPx - fullscreenInsetThickness)
                 / 2);
         float midpointFromBottomPct = (float) fullscreenMidpointFromBottom / deviceProfile.heightPx;
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index ca46aa8..6bc021b 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -36,7 +36,7 @@
 import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
 import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
-import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
 
 import java.util.List;
 
@@ -129,12 +129,22 @@
      * @param dp The device profile, used to report rotation and hardware insets.
      * @param stagePosition 0 if the staging area is pinned to top/left, 1 for bottom/right.
      */
-    void updateStagedSplitIconParams(View out, float onScreenRectCenterX,
+    void updateSplitIconParams(View out, float onScreenRectCenterX,
             float onScreenRectCenterY, float fullscreenScaleX, float fullscreenScaleY,
             int drawableWidth, int drawableHeight, DeviceProfile dp,
             @StagePosition int stagePosition);
 
     /**
+     * Sets positioning and rotation for a SplitInstructionsView.
+     * @param out The SplitInstructionsView that needs to be positioned.
+     * @param dp The device profile, used to report rotation and device type.
+     * @param splitInstructionsHeight The SplitInstructionView's height.
+     * @param splitInstructionsWidth  The SplitInstructionView's width.
+     */
+    void setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight,
+            int splitInstructionsWidth, int threeButtonNavShift);
+
+    /**
      * @param splitDividerSize height of split screen drag handle in portrait, width in landscape
      * @param stagePosition the split position option (top/left, bottom/right) of the first
      *                           task selected for entering split
@@ -153,12 +163,12 @@
      * @param desiredStagePosition Which stage position (topLeft/rightBottom) we want to resize
      *                           outRect for
      */
-    void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect, StagedSplitBounds splitInfo,
+    void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect, SplitBounds splitInfo,
             @SplitConfigurationOptions.StagePosition int desiredStagePosition);
 
     void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot,
             int parentWidth, int parentHeight,
-            StagedSplitBounds splitBoundsConfig, DeviceProfile dp, boolean isRtl);
+            SplitBounds splitBoundsConfig, DeviceProfile dp, boolean isRtl);
 
     // Overview TaskMenuView methods
     void setTaskIconParams(FrameLayout.LayoutParams iconParams,
@@ -166,7 +176,7 @@
     void setSplitIconParams(View primaryIconView, View secondaryIconView,
             int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight,
             int groupedTaskViewHeight, int groupedTaskViewWidth, boolean isRtl,
-            DeviceProfile deviceProfile, StagedSplitBounds splitConfig);
+            DeviceProfile deviceProfile, SplitBounds splitConfig);
 
     /*
      * The following two methods try to center the TaskMenuView in landscape by finding the center
@@ -207,7 +217,7 @@
      * @return A Pair of Floats representing the proper x and y translations.
      */
     Pair<Float, Float> getDwbLayoutTranslations(int taskViewWidth,
-            int taskViewHeight, StagedSplitBounds splitBounds, DeviceProfile deviceProfile,
+            int taskViewHeight, SplitBounds splitBounds, DeviceProfile deviceProfile,
             View[] thumbnailViews, int desiredTaskId, View banner);
 
     // The following are only used by TaskViewTouchHandler.
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index 508823c..181dca5 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -27,6 +27,7 @@
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
 import static com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL;
+import static com.android.launcher3.util.DisplayController.NavigationMode.THREE_BUTTONS;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
 
@@ -47,11 +48,13 @@
 import android.widget.LinearLayout;
 
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
 import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
-import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
 import com.android.launcher3.views.BaseDragLayer;
 
 import java.util.List;
@@ -314,7 +317,7 @@
 
     @Override
     public Pair<Float, Float> getDwbLayoutTranslations(int taskViewWidth,
-            int taskViewHeight, StagedSplitBounds splitBounds, DeviceProfile deviceProfile,
+            int taskViewHeight, SplitBounds splitBounds, DeviceProfile deviceProfile,
             View[] thumbnailViews, int desiredTaskId, View banner) {
         float translationX = 0;
         float translationY = 0;
@@ -467,7 +470,7 @@
     }
 
     @Override
-    public void updateStagedSplitIconParams(View out, float onScreenRectCenterX,
+    public void updateSplitIconParams(View out, float onScreenRectCenterX,
             float onScreenRectCenterY, float fullscreenScaleX, float fullscreenScaleY,
             int drawableWidth, int drawableHeight, DeviceProfile dp,
             @StagePosition int stagePosition) {
@@ -494,6 +497,58 @@
     }
 
     @Override
+    public void setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight,
+            int splitInstructionsWidth, int threeButtonNavShift) {
+        out.setPivotX(0);
+        out.setPivotY(0);
+        out.setRotation(getDegreesRotated());
+        int distanceToEdge;
+        if ((DisplayController.getNavigationMode(out.getContext()) == THREE_BUTTONS)
+                && (dp.isTwoPanels || dp.isTablet)) {
+            // If 3-button nav is active, align the splitInstructionsView with it.
+            distanceToEdge = dp.getTaskbarOffsetY()
+                    + ((dp.taskbarSize - splitInstructionsHeight) / 2);
+        } else {
+            // If 3-button nav is not active, set bottom margin according to spec.
+            if (dp.isPhone) {
+                if (dp.isLandscape) {
+                    distanceToEdge = out.getResources().getDimensionPixelSize(
+                            R.dimen.split_instructions_bottom_margin_phone_landscape);
+                } else {
+                    distanceToEdge = out.getResources().getDimensionPixelSize(
+                            R.dimen.split_instructions_bottom_margin_phone_portrait);
+                }
+            } else if (dp.isTwoPanels) {
+                if (dp.isLandscape) {
+                    distanceToEdge = out.getResources().getDimensionPixelSize(
+                            R.dimen.split_instructions_bottom_margin_twopanels_landscape);
+                } else {
+                    distanceToEdge = out.getResources().getDimensionPixelSize(
+                            R.dimen.split_instructions_bottom_margin_twopanels_portrait);
+                }
+            } else {
+                if (dp.isLandscape) {
+                    distanceToEdge = out.getResources().getDimensionPixelSize(
+                            R.dimen.split_instructions_bottom_margin_tablet_landscape);
+                } else {
+                    distanceToEdge = out.getResources().getDimensionPixelSize(
+                            R.dimen.split_instructions_bottom_margin_tablet_portrait);
+                }
+            }
+        }
+
+        // Center the view in case of unbalanced insets on left or right of screen
+        int insetCorrectionX = (dp.getInsets().right - dp.getInsets().left) / 2;
+        // Adjust for any insets on the bottom edge
+        int insetCorrectionY = dp.getInsets().bottom;
+        out.setTranslationX(insetCorrectionX + threeButtonNavShift);
+        out.setTranslationY(-distanceToEdge + insetCorrectionY);
+        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) out.getLayoutParams();
+        lp.gravity = CENTER_HORIZONTAL | BOTTOM;
+        out.setLayoutParams(lp);
+    }
+
+    @Override
     public void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp,
             @StagePosition int stagePosition, Rect out1, Rect out2) {
         int screenHeight = dp.heightPx;
@@ -525,7 +580,7 @@
 
     @Override
     public void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect,
-            StagedSplitBounds splitInfo, int desiredStagePosition) {
+            SplitBounds splitInfo, int desiredStagePosition) {
         boolean isLandscape = dp.isLandscape;
         float topLeftTaskPercent = splitInfo.appsStackedVertically
                 ? splitInfo.topTaskPercent
@@ -551,7 +606,7 @@
 
     @Override
     public void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot,
-            int parentWidth, int parentHeight, StagedSplitBounds splitBoundsConfig,
+            int parentWidth, int parentHeight, SplitBounds splitBoundsConfig,
             DeviceProfile dp, boolean isRtl) {
         int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
         int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
@@ -611,7 +666,7 @@
     public void setSplitIconParams(View primaryIconView, View secondaryIconView,
             int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight,
             int groupedTaskViewHeight, int groupedTaskViewWidth, boolean isRtl,
-            DeviceProfile deviceProfile, StagedSplitBounds splitConfig) {
+            DeviceProfile deviceProfile, SplitBounds splitConfig) {
         FrameLayout.LayoutParams primaryIconParams =
                 (FrameLayout.LayoutParams) primaryIconView.getLayoutParams();
         FrameLayout.LayoutParams secondaryIconParams =
diff --git a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
index 74b6a5b..339f910 100644
--- a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
@@ -19,6 +19,7 @@
 import static android.view.Gravity.BOTTOM;
 import static android.view.Gravity.CENTER_VERTICAL;
 import static android.view.Gravity.END;
+import static android.view.Gravity.RIGHT;
 import static android.view.Gravity.START;
 
 import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
@@ -37,8 +38,9 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.util.SplitConfigurationOptions;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
-import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds;
 import com.android.launcher3.views.BaseDragLayer;
 
 import java.util.Collections;
@@ -105,11 +107,29 @@
         return new PointF(-margin, margin);
     }
 
+    @Override
+    public void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect, SplitBounds splitInfo,
+            int desiredStagePosition) {
+        float topLeftTaskPercent = splitInfo.appsStackedVertically
+                ? splitInfo.topTaskPercent
+                : splitInfo.leftTaskPercent;
+        float dividerBarPercent = splitInfo.appsStackedVertically
+                ? splitInfo.dividerHeightPercent
+                : splitInfo.dividerWidthPercent;
 
+        // In seascape, the primary thumbnail is counterintuitively placed at the physical bottom of
+        // the screen. This is to preserve consistency when the user rotates: From the user's POV,
+        // the primary should always be on the left.
+        if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) {
+            outRect.top += (int) (outRect.height() * (topLeftTaskPercent + dividerBarPercent));
+        } else {
+            outRect.bottom = outRect.top + (int) (outRect.height() * topLeftTaskPercent);
+        }
+    }
 
     @Override
     public Pair<Float, Float> getDwbLayoutTranslations(int taskViewWidth,
-            int taskViewHeight, StagedSplitBounds splitBounds, DeviceProfile deviceProfile,
+            int taskViewHeight, SplitBounds splitBounds, DeviceProfile deviceProfile,
             View[] thumbnailViews, int desiredTaskId, View banner) {
         boolean isRtl = banner.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
         float translationX = 0;
@@ -167,6 +187,29 @@
     }
 
     @Override
+    public void setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight,
+            int splitInstructionsWidth, int threeButtonNavShift) {
+        out.setPivotX(0);
+        out.setPivotY(0);
+        out.setRotation(getDegreesRotated());
+        int distanceToEdge = out.getResources().getDimensionPixelSize(
+                R.dimen.split_instructions_bottom_margin_phone_landscape);
+        // Adjust for any insets on the right edge
+        int insetCorrectionX = dp.getInsets().right;
+        // Center the view in case of unbalanced insets on top or bottom of screen
+        int insetCorrectionY = (dp.getInsets().bottom - dp.getInsets().top) / 2;
+        out.setTranslationX(splitInstructionsWidth - splitInstructionsHeight - distanceToEdge
+                + insetCorrectionX);
+        out.setTranslationY(((splitInstructionsHeight + splitInstructionsWidth) / 2f)
+                + insetCorrectionY);
+        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) out.getLayoutParams();
+        // Setting gravity to RIGHT instead of the lint-recommended END because we always want this
+        // view to be screen-right when phone is in seascape, regardless of the RtL setting.
+        lp.gravity = RIGHT | CENTER_VERTICAL;
+        out.setLayoutParams(lp);
+    }
+
+    @Override
     public void setTaskIconParams(FrameLayout.LayoutParams iconParams,
             int taskIconMargin, int taskIconHeight, int thumbnailTopMargin, boolean isRtl) {
         iconParams.gravity = (isRtl ? END : START) | CENTER_VERTICAL;
@@ -179,7 +222,7 @@
     public void setSplitIconParams(View primaryIconView, View secondaryIconView,
             int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight,
             int groupedTaskViewHeight, int groupedTaskViewWidth, boolean isRtl,
-            DeviceProfile deviceProfile, StagedSplitBounds splitConfig) {
+            DeviceProfile deviceProfile, SplitBounds splitConfig) {
         super.setSplitIconParams(primaryIconView, secondaryIconView, taskIconHeight,
                 primarySnapshotWidth, primarySnapshotHeight, groupedTaskViewHeight,
                 groupedTaskViewWidth, isRtl, deviceProfile, splitConfig);
@@ -191,7 +234,8 @@
         // We calculate the "midpoint" of the thumbnail area, and place the icons there.
         // This is the place where the thumbnail area splits by default, in a near-50/50 split.
         // It is usually not exactly 50/50, due to insets/screen cutouts.
-        int fullscreenInsetThickness = deviceProfile.getInsets().top;
+        int fullscreenInsetThickness = deviceProfile.getInsets().top
+                - deviceProfile.getInsets().bottom;
         int fullscreenMidpointFromBottom = ((deviceProfile.heightPx
                 - fullscreenInsetThickness) / 2);
         float midpointFromBottomPct = (float) fullscreenMidpointFromBottom / deviceProfile.heightPx;
@@ -208,14 +252,14 @@
         if (splitConfig.initiatedFromSeascape) {
             // if the split was initiated from seascape,
             // the task on the right (secondary) is slightly larger
-            primaryIconView.setTranslationY(-bottomToMidpointOffset - insetOffset);
-            secondaryIconView.setTranslationY(-bottomToMidpointOffset - insetOffset
+            primaryIconView.setTranslationY(-bottomToMidpointOffset - insetOffset
                     + taskIconHeight);
+            secondaryIconView.setTranslationY(-bottomToMidpointOffset - insetOffset);
         } else {
             // if not,
             // the task on the left (primary) is slightly larger
-            primaryIconView.setTranslationY(-bottomToMidpointOffset);
-            secondaryIconView.setTranslationY(-bottomToMidpointOffset + taskIconHeight);
+            primaryIconView.setTranslationY(-bottomToMidpointOffset + taskIconHeight);
+            secondaryIconView.setTranslationY(-bottomToMidpointOffset);
         }
 
         primaryIconView.setLayoutParams(primaryIconParams);
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index 7c73be5..15fe1d9 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -41,7 +41,6 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
-import android.util.Pair;
 import android.view.Display;
 
 import androidx.annotation.AnyThread;
@@ -55,6 +54,7 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Objects;
 import java.util.Set;
@@ -66,6 +66,7 @@
 public class DisplayController implements ComponentCallbacks, SafeCloseable {
 
     private static final String TAG = "DisplayController";
+    private static final boolean DEBUG = false;
 
     public static final MainThreadInitializedObject<DisplayController> INSTANCE =
             new MainThreadInitializedObject<>(DisplayController::new);
@@ -116,8 +117,9 @@
                 getPackageFilter(TARGET_OVERLAY_PACKAGE, ACTION_OVERLAY_CHANGED));
 
         WindowManagerProxy wmProxy = WindowManagerProxy.INSTANCE.get(context);
-        mInfo = new Info(getDisplayInfoContext(display), display,
-                wmProxy, wmProxy.estimateInternalDisplayBounds(context));
+        Context displayInfoContext = getDisplayInfoContext(display);
+        mInfo = new Info(displayInfoContext, wmProxy,
+                wmProxy.estimateInternalDisplayBounds(displayInfoContext));
     }
 
     /**
@@ -215,18 +217,18 @@
         WindowManagerProxy wmProxy = WindowManagerProxy.INSTANCE.get(mContext);
         Info oldInfo = mInfo;
 
-        Context displayContext = getDisplayInfoContext(display);
-        Info newInfo = new Info(displayContext, display, wmProxy, oldInfo.mPerDisplayBounds);
+        Context displayInfoContext = getDisplayInfoContext(display);
+        Info newInfo = new Info(displayInfoContext, wmProxy, oldInfo.mPerDisplayBounds);
 
         if (newInfo.densityDpi != oldInfo.densityDpi || newInfo.fontScale != oldInfo.fontScale
                 || newInfo.navigationMode != oldInfo.navigationMode) {
             // Cache may not be valid anymore, recreate without cache
-            newInfo = new Info(displayContext, display, wmProxy,
-                    wmProxy.estimateInternalDisplayBounds(displayContext));
+            newInfo = new Info(displayInfoContext, wmProxy,
+                    wmProxy.estimateInternalDisplayBounds(displayInfoContext));
         }
 
         int change = 0;
-        if (!newInfo.displayId.equals(oldInfo.displayId)) {
+        if (!newInfo.normalizedDisplayInfo.equals(oldInfo.normalizedDisplayInfo)) {
             change |= CHANGE_ACTIVE_SCREEN;
         }
         if (newInfo.rotation != oldInfo.rotation) {
@@ -238,34 +240,18 @@
         if (newInfo.navigationMode != oldInfo.navigationMode) {
             change |= CHANGE_NAVIGATION_MODE;
         }
-        if (!newInfo.supportedBounds.equals(oldInfo.supportedBounds)) {
+        if (!newInfo.supportedBounds.equals(oldInfo.supportedBounds)
+                || !newInfo.mPerDisplayBounds.equals(oldInfo.mPerDisplayBounds)) {
             change |= CHANGE_SUPPORTED_BOUNDS;
-
-            Point currentS = newInfo.currentSize;
-            Pair<CachedDisplayInfo, WindowBounds[]> cachedBounds =
-                    oldInfo.mPerDisplayBounds.get(newInfo.displayId);
-            Point expectedS = cachedBounds == null ? null : cachedBounds.first.size;
-            if (newInfo.supportedBounds.size() != oldInfo.supportedBounds.size()) {
-                Log.e("b/198965093",
-                        "Inconsistent number of displays"
-                                + "\ndisplay state: " + display.getState()
-                                + "\noldInfo.supportedBounds: " + oldInfo.supportedBounds
-                                + "\nnewInfo.supportedBounds: " + newInfo.supportedBounds);
-            }
-            if (expectedS != null
-                    && (Math.min(currentS.x, currentS.y) != Math.min(expectedS.x, expectedS.y)
-                    || Math.max(currentS.x, currentS.y) != Math.max(expectedS.x, expectedS.y))
-                    && display.getState() == Display.STATE_OFF) {
-                Log.e("b/198965093",
-                        "Display size changed while display is off, ignoring change");
-                return;
-            }
+        }
+        if (DEBUG) {
+            Log.d(TAG, "handleInfoChange - change: 0b" + Integer.toBinaryString(change));
         }
 
         if (change != 0) {
             mInfo = newInfo;
             final int flags = change;
-            MAIN_EXECUTOR.execute(() -> notifyChange(displayContext, flags));
+            MAIN_EXECUTOR.execute(() -> notifyChange(displayInfoContext, flags));
         }
     }
 
@@ -283,8 +269,8 @@
     public static class Info {
 
         // Cached property
+        public final CachedDisplayInfo normalizedDisplayInfo;
         public final int rotation;
-        public final String displayId;
         public final Point currentSize;
         public final Rect cutout;
 
@@ -297,51 +283,66 @@
 
         public final Set<WindowBounds> supportedBounds = new ArraySet<>();
 
-        private final ArrayMap<String, Pair<CachedDisplayInfo, WindowBounds[]>> mPerDisplayBounds =
+        private final ArrayMap<CachedDisplayInfo, WindowBounds[]> mPerDisplayBounds =
                 new ArrayMap<>();
 
-        public Info(Context context, Display display) {
+        public Info(Context displayInfoContext) {
             /* don't need system overrides for external displays */
-            this(context, display, new WindowManagerProxy(), new ArrayMap<>());
+            this(displayInfoContext, new WindowManagerProxy(), new ArrayMap<>());
         }
 
         // Used for testing
-        public Info(Context context, Display display,
+        public Info(Context displayInfoContext,
                 WindowManagerProxy wmProxy,
-                ArrayMap<String, Pair<CachedDisplayInfo, WindowBounds[]>> perDisplayBoundsCache) {
-            CachedDisplayInfo displayInfo = wmProxy.getDisplayInfo(context, display);
+                ArrayMap<CachedDisplayInfo, WindowBounds[]> perDisplayBoundsCache) {
+            CachedDisplayInfo displayInfo = wmProxy.getDisplayInfo(displayInfoContext);
+            normalizedDisplayInfo = displayInfo.normalize();
             rotation = displayInfo.rotation;
             currentSize = displayInfo.size;
-            displayId = displayInfo.id;
             cutout = displayInfo.cutout;
 
-            Configuration config = context.getResources().getConfiguration();
+            Configuration config = displayInfoContext.getResources().getConfiguration();
             fontScale = config.fontScale;
             densityDpi = config.densityDpi;
             mScreenSizeDp = new PortraitSize(config.screenHeightDp, config.screenWidthDp);
-            navigationMode = parseNavigationMode(context);
+            navigationMode = parseNavigationMode(displayInfoContext);
 
             mPerDisplayBounds.putAll(perDisplayBoundsCache);
-            Pair<CachedDisplayInfo, WindowBounds[]> cachedValue = mPerDisplayBounds.get(displayId);
+            WindowBounds[] cachedValue = mPerDisplayBounds.get(normalizedDisplayInfo);
 
-            WindowBounds realBounds = wmProxy.getRealBounds(context, display, displayInfo);
+            WindowBounds realBounds = wmProxy.getRealBounds(displayInfoContext, displayInfo);
             if (cachedValue == null) {
-                supportedBounds.add(realBounds);
-            } else {
+                // Unexpected normalizedDisplayInfo is found, recreate the cache
+                Log.e(TAG, "Unexpected normalizedDisplayInfo found, invalidating cache");
+                mPerDisplayBounds.clear();
+                mPerDisplayBounds.putAll(wmProxy.estimateInternalDisplayBounds(displayInfoContext));
+                cachedValue = mPerDisplayBounds.get(normalizedDisplayInfo);
+                if (cachedValue == null) {
+                    Log.e(TAG, "normalizedDisplayInfo not found in estimation: "
+                            + normalizedDisplayInfo);
+                    supportedBounds.add(realBounds);
+                }
+            }
+
+            if (cachedValue != null) {
                 // Verify that the real bounds are a match
-                WindowBounds expectedBounds = cachedValue.second[displayInfo.rotation];
+                WindowBounds expectedBounds = cachedValue[displayInfo.rotation];
                 if (!realBounds.equals(expectedBounds)) {
                     WindowBounds[] clone = new WindowBounds[4];
-                    System.arraycopy(cachedValue.second, 0, clone, 0, 4);
+                    System.arraycopy(cachedValue, 0, clone, 0, 4);
                     clone[displayInfo.rotation] = realBounds;
-                    cachedValue = Pair.create(displayInfo.normalize(), clone);
-                    mPerDisplayBounds.put(displayId, cachedValue);
+                    mPerDisplayBounds.put(normalizedDisplayInfo, clone);
                 }
             }
             mPerDisplayBounds.values().forEach(
-                    pair -> Collections.addAll(supportedBounds, pair.second));
-            Log.d("b/211775278", "displayId: " + displayId + ", currentSize: " + currentSize);
-            Log.d("b/211775278", "perDisplayBounds: " + mPerDisplayBounds);
+                    windowBounds -> Collections.addAll(supportedBounds, windowBounds));
+            if (DEBUG) {
+                Log.d(TAG, "displayInfo: " + displayInfo);
+                Log.d(TAG, "realBounds: " + realBounds);
+                Log.d(TAG, "normalizedDisplayInfo: " + normalizedDisplayInfo);
+                mPerDisplayBounds.forEach((key, value) -> Log.d(TAG,
+                        "perDisplayBounds - " + key + ": " + Arrays.deepToString(value)));
+            }
         }
 
         /**
@@ -369,13 +370,14 @@
     public void dump(PrintWriter pw) {
         Info info = mInfo;
         pw.println("DisplayController.Info:");
-        pw.println("  id=" + info.displayId);
+        pw.println("  normalizedDisplayInfo=" + info.normalizedDisplayInfo);
         pw.println("  rotation=" + info.rotation);
         pw.println("  fontScale=" + info.fontScale);
         pw.println("  densityDpi=" + info.densityDpi);
         pw.println("  navigationMode=" + info.navigationMode.name());
         pw.println("  currentSize=" + info.currentSize);
-        pw.println("  supportedBounds=" + info.supportedBounds);
+        info.mPerDisplayBounds.forEach((key, value) -> pw.println(
+                "  perDisplayBounds - " + key + ": " + Arrays.deepToString(value)));
     }
 
     /**
diff --git a/src/com/android/launcher3/util/HorizontalInsettableView.java b/src/com/android/launcher3/util/HorizontalInsettableView.java
index 7979bc0..486b73d 100644
--- a/src/com/android/launcher3/util/HorizontalInsettableView.java
+++ b/src/com/android/launcher3/util/HorizontalInsettableView.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.util;
 
+import android.util.FloatProperty;
+
 /**
  * Allows the implementing view to add insets to the left and right.
  */
@@ -32,4 +34,22 @@
      */
     void setHorizontalInsets(float insetPercentage);
 
+    /**
+     * Returns the width percentage to inset the content from the left and from the right. See
+     * {@link #setHorizontalInsets};
+     */
+    float getHorizontalInsets();
+
+    FloatProperty<HorizontalInsettableView> HORIZONTAL_INSETS =
+            new FloatProperty<HorizontalInsettableView>("horizontalInsets") {
+                @Override
+                public Float get(HorizontalInsettableView view) {
+                    return view.getHorizontalInsets();
+                }
+
+                @Override
+                public void setValue(HorizontalInsettableView view, float insetPercentage) {
+                    view.setHorizontalInsets(insetPercentage);
+                }
+            };
 }
diff --git a/src/com/android/launcher3/util/PendingSplitSelectInfo.java b/src/com/android/launcher3/util/PendingSplitSelectInfo.java
new file mode 100644
index 0000000..ed02465
--- /dev/null
+++ b/src/com/android/launcher3/util/PendingSplitSelectInfo.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util;
+
+import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
+
+/**
+ * Utility class to store information regarding a split select request. This includes the taskId of
+ * the originating task, plus the stage position.
+ * This information is intended to be saved across launcher instances, e.g. when Launcher needs to
+ * recover straight into a split select state.
+ */
+public class PendingSplitSelectInfo {
+
+    private final int mStagedTaskId;
+    private final int mStagePosition;
+
+    public PendingSplitSelectInfo(int stagedTaskId, int stagePosition) {
+        this.mStagedTaskId = stagedTaskId;
+        this.mStagePosition = stagePosition;
+    }
+
+    public int getStagedTaskId() {
+        return mStagedTaskId;
+    }
+
+    public @StagePosition int getStagePosition() {
+        return mStagePosition;
+    }
+}
diff --git a/src/com/android/launcher3/util/SplitConfigurationOptions.java b/src/com/android/launcher3/util/SplitConfigurationOptions.java
index 6a336cc..f14d985 100644
--- a/src/com/android/launcher3/util/SplitConfigurationOptions.java
+++ b/src/com/android/launcher3/util/SplitConfigurationOptions.java
@@ -96,7 +96,7 @@
      *
      * If you make changes here, consider making the same changes there
      */
-    public static class StagedSplitBounds {
+    public static class SplitBounds {
         public final Rect leftTopBounds;
         public final Rect rightBottomBounds;
         /** This rect represents the actual gap between the two apps */
@@ -124,7 +124,7 @@
         public final int leftTopTaskId;
         public final int rightBottomTaskId;
 
-        public StagedSplitBounds(Rect leftTopBounds, Rect rightBottomBounds, int leftTopTaskId,
+        public SplitBounds(Rect leftTopBounds, Rect rightBottomBounds, int leftTopTaskId,
                 int rightBottomTaskId) {
             this.leftTopBounds = leftTopBounds;
             this.rightBottomBounds = rightBottomBounds;
@@ -163,7 +163,7 @@
         }
     }
 
-    public static class StagedSplitTaskPosition {
+    public static class SplitStageInfo {
         public int taskId = -1;
         @StagePosition
         public int stagePosition = STAGE_POSITION_UNDEFINED;
diff --git a/src/com/android/launcher3/util/SystemUiController.java b/src/com/android/launcher3/util/SystemUiController.java
index 630df7e..6945983 100644
--- a/src/com/android/launcher3/util/SystemUiController.java
+++ b/src/com/android/launcher3/util/SystemUiController.java
@@ -19,6 +19,10 @@
 import android.view.View;
 import android.view.Window;
 
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
 
 /**
@@ -31,15 +35,26 @@
     public static final int UI_STATE_SCRIM_VIEW = 1;
     public static final int UI_STATE_WIDGET_BOTTOM_SHEET = 2;
     public static final int UI_STATE_FULLSCREEN_TASK = 3;
-    public static final int UI_STATE_ALLAPPS = 4;
 
     public static final int FLAG_LIGHT_NAV = 1 << 0;
     public static final int FLAG_DARK_NAV = 1 << 1;
     public static final int FLAG_LIGHT_STATUS = 1 << 2;
     public static final int FLAG_DARK_STATUS = 1 << 3;
 
+    /**
+     * Security type based on WifiConfiguration.KeyMgmt
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, value = {
+            FLAG_LIGHT_NAV,
+            FLAG_DARK_NAV,
+            FLAG_LIGHT_STATUS,
+            FLAG_DARK_STATUS,
+    })
+    public @interface SystemUiControllerFlags {}
+
     private final Window mWindow;
-    private final int[] mStates = new int[5];
+    private final int[] mStates = new int[4];
 
     public SystemUiController(Window window) {
         mWindow = window;
@@ -50,7 +65,7 @@
                 ? (FLAG_LIGHT_NAV | FLAG_LIGHT_STATUS) : (FLAG_DARK_NAV | FLAG_DARK_STATUS));
     }
 
-    public void updateUiState(int uiState, int flags) {
+    public void updateUiState(int uiState, @SystemUiControllerFlags int flags) {
         if (mStates[uiState] == flags) {
             return;
         }
diff --git a/src/com/android/launcher3/util/ViewCapture.java b/src/com/android/launcher3/util/ViewCapture.java
new file mode 100644
index 0000000..140971b
--- /dev/null
+++ b/src/com/android/launcher3/util/ViewCapture.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util;
+
+import android.content.res.Resources;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.util.Base64;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver.OnDrawListener;
+
+import androidx.annotation.UiThread;
+
+import com.android.launcher3.view.ViewCaptureData.ExportedData;
+import com.android.launcher3.view.ViewCaptureData.FrameData;
+import com.android.launcher3.view.ViewCaptureData.ViewNode;
+
+import java.util.concurrent.FutureTask;
+
+/**
+ * Utility class for capturing view data every frame
+ */
+public class ViewCapture implements OnDrawListener {
+
+    private static final String TAG = "ViewCapture";
+
+    private static final int MEMORY_SIZE = 2000;
+
+    private final View mRoot;
+    private final long[] mFrameTimes = new long[MEMORY_SIZE];
+    private final Node[] mNodes = new Node[MEMORY_SIZE];
+
+    private int mFrameIndex = -1;
+
+    /**
+     * @param root the root view for the capture data
+     */
+    public ViewCapture(View root) {
+        mRoot = root;
+    }
+
+    @Override
+    public void onDraw() {
+        Trace.beginSection("view_capture");
+        long now = SystemClock.elapsedRealtimeNanos();
+
+        mFrameIndex++;
+        if (mFrameIndex >= MEMORY_SIZE) {
+            mFrameIndex = 0;
+        }
+        mFrameTimes[mFrameIndex] = now;
+        mNodes[mFrameIndex] = captureView(mRoot, mNodes[mFrameIndex]);
+        Trace.endSection();
+    }
+
+    /**
+     * Creates a proto of all the data captured so far.
+     */
+    public String dumpToString() {
+        Handler handler = mRoot.getHandler();
+        if (handler == null) {
+            handler = Executors.MAIN_EXECUTOR.getHandler();
+        }
+        FutureTask<ExportedData> task = new FutureTask<>(this::dumpToProtoUI);
+        if (Looper.myLooper() == handler.getLooper()) {
+            task.run();
+        } else {
+            handler.post(task);
+        }
+        try {
+            return Base64.encodeToString(task.get().toByteArray(),
+                    Base64.NO_CLOSE | Base64.NO_PADDING | Base64.NO_WRAP);
+        } catch (Exception e) {
+            Log.e(TAG, "Error capturing proto", e);
+            return "--error--";
+        }
+    }
+
+    @UiThread
+    private ExportedData dumpToProtoUI() {
+        ExportedData.Builder dataBuilder = ExportedData.newBuilder();
+        Resources res = mRoot.getResources();
+
+        int size = (mNodes[MEMORY_SIZE - 1] == null) ? mFrameIndex + 1 : MEMORY_SIZE;
+        for (int i = size - 1; i >= 0; i--) {
+            int index = (MEMORY_SIZE + mFrameIndex - i) % MEMORY_SIZE;
+            dataBuilder.addFrameData(FrameData.newBuilder()
+                    .setNode(mNodes[index].toProto(res))
+                    .setTimestamp(mFrameTimes[index]));
+        }
+        return dataBuilder.build();
+    }
+
+    private Node captureView(View view, Node recycle) {
+        Node result = recycle == null ? new Node() : recycle;
+
+        result.clazz = view.getClass();
+        result.hashCode = view.hashCode();
+        result.id = view.getId();
+        result.left = view.getLeft();
+        result.top = view.getTop();
+        result.right = view.getRight();
+        result.bottom = view.getBottom();
+        result.scrollX = view.getScrollX();
+        result.scrollY = view.getScrollY();
+
+        result.translateX = view.getTranslationX();
+        result.translateY = view.getTranslationY();
+        result.scaleX = view.getScaleX();
+        result.scaleY = view.getScaleY();
+        result.alpha = view.getAlpha();
+
+        result.visibility = view.getVisibility();
+        result.willNotDraw = view.willNotDraw();
+
+        if (view instanceof ViewGroup) {
+            ViewGroup parent = (ViewGroup) view;
+            result.clipChildren = parent.getClipChildren();
+            int childCount = parent.getChildCount();
+            if (childCount == 0) {
+                result.children = null;
+            } else {
+                result.children = captureView(parent.getChildAt(0), result.children);
+                Node lastChild = result.children;
+                for (int i = 1; i < childCount; i++) {
+                    lastChild.sibling = captureView(parent.getChildAt(i), lastChild.sibling);
+                    lastChild = lastChild.sibling;
+                }
+                lastChild.sibling = null;
+            }
+        } else {
+            result.clipChildren = false;
+            result.children = null;
+        }
+        return result;
+    }
+
+    private static class Node {
+
+        // We store reference in memory to avoid generating and storing too many strings
+        public Class clazz;
+        public int hashCode;
+
+        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 int visibility;
+        public boolean willNotDraw;
+        public boolean clipChildren;
+
+        public Node sibling;
+        public Node children;
+
+        public ViewNode toProto(Resources res) {
+            String resolvedId;
+            if (id >= 0) {
+                try {
+                    resolvedId = res.getResourceTypeName(id) + '/' + res.getResourceEntryName(id);
+                } catch (Resources.NotFoundException e) {
+                    resolvedId = "id/" + "0x" + Integer.toHexString(id).toUpperCase();
+                }
+            } else {
+                resolvedId = "NO_ID";
+            }
+
+            ViewNode.Builder result = ViewNode.newBuilder()
+                    .setClassname(clazz.getName() + "@" + hashCode)
+                    .setId(resolvedId)
+                    .setLeft(left)
+                    .setTop(top)
+                    .setWidth(right - left)
+                    .setHeight(bottom - top)
+                    .setTranslationX(translateX)
+                    .setTranslationY(translateY)
+                    .setScaleX(scaleX)
+                    .setScaleY(scaleY)
+                    .setAlpha(alpha)
+                    .setVisibility(visibility)
+                    .setWillNotDraw(willNotDraw)
+                    .setClipChildren(clipChildren);
+            Node child = children;
+            while (child != null) {
+                result.addChildren(child.toProto(res));
+                child = child.sibling;
+            }
+            return result.build();
+        }
+
+    }
+}
diff --git a/src/com/android/launcher3/util/WindowBounds.java b/src/com/android/launcher3/util/WindowBounds.java
index a15679a..91480e1 100644
--- a/src/com/android/launcher3/util/WindowBounds.java
+++ b/src/com/android/launcher3/util/WindowBounds.java
@@ -76,6 +76,7 @@
                 + "bounds=" + bounds
                 + ", insets=" + insets
                 + ", availableSize=" + availableSize
+                + ", rotationHint=" + rotationHint
                 + '}';
     }
 
diff --git a/src/com/android/launcher3/util/window/CachedDisplayInfo.java b/src/com/android/launcher3/util/window/CachedDisplayInfo.java
index 06b9829..23f37aa 100644
--- a/src/com/android/launcher3/util/window/CachedDisplayInfo.java
+++ b/src/com/android/launcher3/util/window/CachedDisplayInfo.java
@@ -30,7 +30,6 @@
  */
 public class CachedDisplayInfo {
 
-    public final String id;
     public final Point size;
     public final int rotation;
     public final Rect cutout;
@@ -40,11 +39,10 @@
     }
 
     public CachedDisplayInfo(Point size, int rotation) {
-        this("", size, rotation, new Rect());
+        this(size, rotation, new Rect());
     }
 
-    public CachedDisplayInfo(String id, Point size, int rotation, Rect cutout) {
-        this.id = id;
+    public CachedDisplayInfo(Point size, int rotation, Rect cutout) {
         this.size = size;
         this.rotation = rotation;
         this.cutout = cutout;
@@ -62,16 +60,15 @@
 
         Rect newCutout = new Rect(cutout);
         rotateRect(newCutout, deltaRotation(rotation, Surface.ROTATION_0));
-        return new CachedDisplayInfo(id, newSize, Surface.ROTATION_0, newCutout);
+        return new CachedDisplayInfo(newSize, Surface.ROTATION_0, newCutout);
     }
 
     @Override
     public String toString() {
         return "CachedDisplayInfo{"
-                + "id='" + id + '\''
-                + ", size=" + size
-                + ", rotation=" + rotation
+                + "size=" + size
                 + ", cutout=" + cutout
+                + ", rotation=" + rotation
                 + '}';
     }
 
@@ -80,13 +77,13 @@
         if (this == o) return true;
         if (!(o instanceof CachedDisplayInfo)) return false;
         CachedDisplayInfo that = (CachedDisplayInfo) o;
-        return rotation == that.rotation && Objects.equals(id, that.id)
-                && Objects.equals(size, that.size) && Objects.equals(cutout,
-                that.cutout);
+        return rotation == that.rotation
+                && Objects.equals(size, that.size)
+                && Objects.equals(cutout, that.cutout);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(id, size, rotation, cutout);
+        return Objects.hash(size, rotation, cutout);
     }
 }
diff --git a/src/com/android/launcher3/util/window/WindowManagerProxy.java b/src/com/android/launcher3/util/window/WindowManagerProxy.java
index 9665bf9..d5a065a 100644
--- a/src/com/android/launcher3/util/window/WindowManagerProxy.java
+++ b/src/com/android/launcher3/util/window/WindowManagerProxy.java
@@ -16,7 +16,6 @@
 package com.android.launcher3.util.window;
 
 import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 
 import static com.android.launcher3.ResourceUtils.INVALID_RESOURCE_HANDLE;
 import static com.android.launcher3.ResourceUtils.NAVBAR_HEIGHT;
@@ -41,7 +40,6 @@
 import android.hardware.display.DisplayManager;
 import android.os.Build;
 import android.util.ArrayMap;
-import android.util.Pair;
 import android.view.Display;
 import android.view.DisplayCutout;
 import android.view.Surface;
@@ -88,20 +86,12 @@
      * Returns a map of normalized info of internal displays to estimated window bounds
      * for that display
      */
-    public ArrayMap<String, Pair<CachedDisplayInfo, WindowBounds[]>> estimateInternalDisplayBounds(
-            Context context) {
-        Display[] displays = getDisplays(context);
-        ArrayMap<String, Pair<CachedDisplayInfo, WindowBounds[]>> result = new ArrayMap<>();
-        for (Display display : displays) {
-            if (isInternalDisplay(display)) {
-                Context displayContext = Utilities.ATLEAST_S
-                        ? context.createWindowContext(display, TYPE_APPLICATION, null)
-                        : context.createDisplayContext(display);
-                CachedDisplayInfo info = getDisplayInfo(displayContext, display).normalize();
-                WindowBounds[] bounds = estimateWindowBounds(context, info);
-                result.put(info.id, Pair.create(info, bounds));
-            }
-        }
+    public ArrayMap<CachedDisplayInfo, WindowBounds[]> estimateInternalDisplayBounds(
+            Context displayInfoContext) {
+        CachedDisplayInfo info = getDisplayInfo(displayInfoContext).normalize();
+        WindowBounds[] bounds = estimateWindowBounds(displayInfoContext, info);
+        ArrayMap<CachedDisplayInfo, WindowBounds[]> result = new ArrayMap<>();
+        result.put(info, bounds);
         return result;
     }
 
@@ -109,12 +99,11 @@
      * Returns the real bounds for the provided display after applying any insets normalization
      */
     @TargetApi(Build.VERSION_CODES.R)
-    public WindowBounds getRealBounds(Context windowContext,
-            Display display, CachedDisplayInfo info) {
+    public WindowBounds getRealBounds(Context displayInfoContext, CachedDisplayInfo info) {
         if (!Utilities.ATLEAST_R) {
             Point smallestSize = new Point();
             Point largestSize = new Point();
-            display.getCurrentSizeRange(smallestSize, largestSize);
+            getDisplay(displayInfoContext).getCurrentSizeRange(smallestSize, largestSize);
 
             if (info.size.y > info.size.x) {
                 // Portrait
@@ -122,17 +111,16 @@
                         info.rotation);
             } else {
                 // Landscape
-                new WindowBounds(info.size.x, info.size.y, largestSize.x, smallestSize.y,
+                return new WindowBounds(info.size.x, info.size.y, largestSize.x, smallestSize.y,
                         info.rotation);
             }
         }
 
-        WindowMetrics wm = windowContext.getSystemService(WindowManager.class)
+        WindowMetrics windowMetrics = displayInfoContext.getSystemService(WindowManager.class)
                 .getMaximumWindowMetrics();
-
         Rect insets = new Rect();
-        normalizeWindowInsets(windowContext, wm.getWindowInsets(), insets);
-        return new WindowBounds(wm.getBounds(), insets, info.rotation);
+        normalizeWindowInsets(displayInfoContext, windowMetrics.getWindowInsets(), insets);
+        return new WindowBounds(windowMetrics.getBounds(), insets, info.rotation);
     }
 
     /**
@@ -169,12 +157,9 @@
         insetsBuilder.setInsetsIgnoringVisibility(WindowInsets.Type.navigationBars(), newNavInsets);
 
         Insets statusBarInsets = oldInsets.getInsets(WindowInsets.Type.statusBars());
-
-
         int statusBarHeight = getDimenByName(systemRes,
                 (isPortrait) ? STATUS_BAR_HEIGHT_PORTRAIT : STATUS_BAR_HEIGHT_LANDSCAPE,
                 STATUS_BAR_HEIGHT);
-
         Insets newStatusBarInsets = Insets.of(
                 statusBarInsets.left,
                 Math.max(statusBarInsets.top, statusBarHeight),
@@ -202,21 +187,14 @@
     }
 
     /**
-     * Returns true if the display is an internal displays
-     */
-    protected boolean isInternalDisplay(Display display) {
-        return display.getDisplayId() == Display.DEFAULT_DISPLAY;
-    }
-
-    /**
      * Returns a list of possible WindowBounds for the display keyed on the 4 surface rotations
      */
-    public WindowBounds[] estimateWindowBounds(Context context, CachedDisplayInfo display) {
+    protected WindowBounds[] estimateWindowBounds(Context context, CachedDisplayInfo displayInfo) {
         int densityDpi = context.getResources().getConfiguration().densityDpi;
-        int rotation = display.rotation;
-        Rect safeCutout = display.cutout;
+        int rotation = displayInfo.rotation;
+        Rect safeCutout = displayInfo.cutout;
 
-        int minSize = Math.min(display.size.x, display.size.y);
+        int minSize = Math.min(displayInfo.size.x, displayInfo.size.y);
         int swDp = (int) dpiFromPx(minSize, densityDpi);
 
         Resources systemRes;
@@ -255,7 +233,7 @@
         Point tempSize = new Point();
         for (int i = 0; i < 4; i++) {
             int rotationChange = deltaRotation(rotation, i);
-            tempSize.set(display.size.x, display.size.y);
+            tempSize.set(displayInfo.size.x, displayInfo.size.y);
             rotateSize(tempSize, rotationChange);
             Rect bounds = new Rect(0, 0, tempSize.x, tempSize.y);
 
@@ -311,55 +289,58 @@
      * Returns a CachedDisplayInfo initialized for the current display
      */
     @TargetApi(Build.VERSION_CODES.S)
-    public CachedDisplayInfo getDisplayInfo(Context displayContext, Display display) {
-        int rotation = getRotation(displayContext);
-        Rect cutoutRect = new Rect();
-        Point size = new Point();
+    public CachedDisplayInfo getDisplayInfo(Context displayInfoContext) {
+        int rotation = getRotation(displayInfoContext);
         if (Utilities.ATLEAST_S) {
-            WindowMetrics wm = displayContext.getSystemService(WindowManager.class)
+            WindowMetrics windowMetrics = displayInfoContext.getSystemService(WindowManager.class)
                     .getMaximumWindowMetrics();
-            DisplayCutout cutout = wm.getWindowInsets().getDisplayCutout();
-            if (cutout != null) {
-                cutoutRect.set(cutout.getSafeInsetLeft(), cutout.getSafeInsetTop(),
-                        cutout.getSafeInsetRight(), cutout.getSafeInsetBottom());
-            }
-
-            size.set(wm.getBounds().right, wm.getBounds().bottom);
+            return getDisplayInfo(windowMetrics, rotation);
         } else {
+            Point size = new Point();
+            Display display = getDisplay(displayInfoContext);
             display.getRealSize(size);
+            Rect cutoutRect = new Rect();
+            return new CachedDisplayInfo(size, rotation, cutoutRect);
         }
-        return new CachedDisplayInfo(getDisplayId(display), size, rotation, cutoutRect);
     }
 
     /**
-     * Returns a unique ID representing the display
+     * Returns a CachedDisplayInfo initialized for the current display
      */
-    protected String getDisplayId(Display display) {
-        return Integer.toString(display.getDisplayId());
+    @TargetApi(Build.VERSION_CODES.S)
+    protected CachedDisplayInfo getDisplayInfo(WindowMetrics windowMetrics, int rotation) {
+        Point size = new Point(windowMetrics.getBounds().right, windowMetrics.getBounds().bottom);
+        Rect cutoutRect = new Rect();
+        DisplayCutout cutout = windowMetrics.getWindowInsets().getDisplayCutout();
+        if (cutout != null) {
+            cutoutRect.set(cutout.getSafeInsetLeft(), cutout.getSafeInsetTop(),
+                    cutout.getSafeInsetRight(), cutout.getSafeInsetBottom());
+        }
+        return new CachedDisplayInfo(size, rotation, cutoutRect);
     }
 
     /**
-     * Returns rotation of the display associated with the context.
+     * Returns rotation of the display associated with the context, or rotation of DEFAULT_DISPLAY
+     * if the context isn't associated with a display.
      */
-    public int getRotation(Context context) {
-        Display d = null;
+    public int getRotation(Context displayInfoContext) {
+        return getDisplay(displayInfoContext).getRotation();
+    }
+
+    /**
+     *
+     * Returns the display associated with the context, or DEFAULT_DISPLAY if the context isn't
+     * associated with a display.
+     */
+    protected Display getDisplay(Context displayInfoContext) {
         if (Utilities.ATLEAST_R) {
             try {
-                d = context.getDisplay();
+                return displayInfoContext.getDisplay();
             } catch (UnsupportedOperationException e) {
                 // Ignore
             }
         }
-        if (d == null) {
-            d = context.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY);
-        }
-        return d.getRotation();
-    }
-
-    /**
-     * Returns all currently valid logical displays.
-     */
-    protected Display[] getDisplays(Context context) {
-        return context.getSystemService(DisplayManager.class).getDisplays();
+        return displayInfoContext.getSystemService(DisplayManager.class).getDisplay(
+                DEFAULT_DISPLAY);
     }
 }
diff --git a/src/com/android/launcher3/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/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/LauncherAppWidgetHost.java b/src/com/android/launcher3/widget/LauncherAppWidgetHost.java
index fe83f3f..98a960c 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHost.java
@@ -28,6 +28,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.util.SparseArray;
+import android.widget.RemoteViews;
 import android.widget.Toast;
 
 import androidx.annotation.Nullable;
@@ -37,6 +38,7 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.testing.TestLogging;
@@ -70,13 +72,14 @@
     private final ArrayList<ProviderChangedListener> mProviderChangeListeners = new ArrayList<>();
     private final SparseArray<LauncherAppWidgetHostView> mViews = new SparseArray<>();
     private final SparseArray<PendingAppWidgetHostView> mPendingViews = new SparseArray<>();
+    private final SparseArray<LauncherAppWidgetHostView> mDeferredViews = new SparseArray<>();
+    private final SparseArray<RemoteViews> mCachedRemoteViews = new SparseArray<>();
 
     private final Context mContext;
     private int mFlags = FLAG_STATE_IS_NORMAL;
 
     private IntConsumer mAppWidgetRemovedCallback = null;
 
-
     public LauncherAppWidgetHost(Context context) {
         this(context, null);
     }
@@ -95,6 +98,11 @@
         if (mPendingViews.get(appWidgetId) != null) {
             view = mPendingViews.get(appWidgetId);
             mPendingViews.remove(appWidgetId);
+        } else if (mDeferredViews.get(appWidgetId) != null) {
+            // In case the widget view is deferred, we will simply return the deferred view as
+            // opposed to instantiate a new instance of LauncherAppWidgetHostView since launcher
+            // already added the former to the workspace.
+            view = mDeferredViews.get(appWidgetId);
         } else {
             view = new LauncherAppWidgetHostView(context);
         }
@@ -120,12 +128,25 @@
             // widgets upon bind anyway. See issue 14255011 for more context.
         }
 
-        // We go in reverse order and inflate any deferred widget
+        // We go in reverse order and inflate any deferred or cached widget
         for (int i = mViews.size() - 1; i >= 0; i--) {
             LauncherAppWidgetHostView view = mViews.valueAt(i);
             if (view instanceof DeferredAppWidgetHostView) {
                 view.reInflate();
             }
+            if (FeatureFlags.ENABLE_CACHED_WIDGET.get()) {
+                final int appWidgetId = mViews.keyAt(i);
+                if (view == mDeferredViews.get(appWidgetId)) {
+                    // If the widget view was deferred, we'll need to call super.createView here
+                    // to make the binder call to system process to fetch cumulative updates to this
+                    // widget, as well as setting up this view for future updates.
+                    super.createView(view.mLauncher, appWidgetId, view.getAppWidgetInfo());
+                    // At this point #onCreateView should have been called, which in turn returned
+                    // the deferred view. There's no reason to keep the reference anymore, so we
+                    // removed it here.
+                    mDeferredViews.remove(appWidgetId);
+                }
+            }
         }
     }
 
@@ -221,10 +242,28 @@
             CustomWidgetManager.INSTANCE.get(context).onViewCreated(lahv);
             return lahv;
         } else if ((mFlags & FLAG_LISTENING) == 0) {
-            DeferredAppWidgetHostView view = new DeferredAppWidgetHostView(context);
-            view.setAppWidget(appWidgetId, appWidget);
-            mViews.put(appWidgetId, view);
-            return view;
+            // Since the launcher hasn't started listening to widget updates, we can't simply call
+            // super.createView here because the later will make a binder call to retrieve
+            // RemoteViews from system process.
+            // TODO: have launcher always listens to widget updates in background so that this
+            //  check can be removed altogether.
+            if (FeatureFlags.ENABLE_CACHED_WIDGET.get()
+                    && mCachedRemoteViews.get(appWidgetId) != null) {
+                // We've found RemoteViews from cache for this widget, so we will instantiate a
+                // widget host view and populate it with the cached RemoteViews.
+                final LauncherAppWidgetHostView view = new LauncherAppWidgetHostView(context);
+                view.setAppWidget(appWidgetId, appWidget);
+                view.updateAppWidget(mCachedRemoteViews.get(appWidgetId));
+                mDeferredViews.put(appWidgetId, view);
+                mViews.put(appWidgetId, view);
+                return view;
+            } else {
+                // When cache misses, a placeholder for the widget will be returned instead.
+                DeferredAppWidgetHostView view = new DeferredAppWidgetHostView(context);
+                view.setAppWidget(appWidgetId, appWidget);
+                mViews.put(appWidgetId, view);
+                return view;
+            }
         } else {
             try {
                 return super.createView(context, appWidgetId, appWidget);
@@ -281,6 +320,16 @@
     @Override
     public void clearViews() {
         super.clearViews();
+        if (FeatureFlags.ENABLE_CACHED_WIDGET.get()) {
+            // First, we clear any previously cached content from existing widgets
+            mCachedRemoteViews.clear();
+            // Then we proceed to cache the content from the widgets
+            for (int i = 0; i < mViews.size(); i++) {
+                final int appWidgetId = mViews.keyAt(i);
+                final LauncherAppWidgetHostView view = mViews.get(appWidgetId);
+                mCachedRemoteViews.put(appWidgetId, view.mLastRemoteViews);
+            }
+        }
         mViews.clear();
     }
 
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index 0865152..fc1e880 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -43,6 +43,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -85,7 +86,7 @@
     private Runnable mAutoAdvanceRunnable;
 
     private long mDeferUpdatesUntilMillis = 0;
-    private RemoteViews mDeferredRemoteViews;
+    RemoteViews mLastRemoteViews;
     private boolean mHasDeferredColorChange = false;
     private @Nullable SparseIntArray mDeferredColorChange = null;
 
@@ -150,11 +151,18 @@
                     TRACE_METHOD_NAME + getAppWidgetInfo().provider, getAppWidgetId());
             mTrackingWidgetUpdate = false;
         }
-        if (isDeferringUpdates()) {
-            mDeferredRemoteViews = remoteViews;
-            return;
+        if (FeatureFlags.ENABLE_CACHED_WIDGET.get()) {
+            mLastRemoteViews = remoteViews;
+            if (isDeferringUpdates()) {
+                return;
+            }
+        } else {
+            if (isDeferringUpdates()) {
+                mLastRemoteViews = remoteViews;
+                return;
+            }
+            mLastRemoteViews = null;
         }
-        mDeferredRemoteViews = null;
 
         super.updateAppWidget(remoteViews);
 
@@ -218,8 +226,7 @@
         SparseIntArray deferredColors;
         boolean hasDeferredColors;
         mDeferUpdatesUntilMillis = 0;
-        remoteViews = mDeferredRemoteViews;
-        mDeferredRemoteViews = null;
+        remoteViews = mLastRemoteViews;
         deferredColors = mDeferredColorChange;
         hasDeferredColors = mHasDeferredColorChange;
         mDeferredColorChange = null;
diff --git a/src/com/android/launcher3/widget/picker/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..88d9723 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -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;
@@ -161,7 +163,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 +222,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 +272,7 @@
             reset();
             resetExpandedHeaders();
             mCurrentWidgetsRecyclerView = recyclerView;
-            mSearchScrollController.setCurrentRecyclerView(recyclerView);
+            mSearchScrollView.setCurrentRecyclerView(recyclerView);
         }
     }
 
@@ -285,7 +299,7 @@
             mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView.scrollToTop();
         }
         mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.scrollToTop();
-        mSearchScrollController.reset(/* animate= */ true);
+        mSearchScrollView.reset(/* animate= */ true);
     }
 
     @VisibleForTesting
@@ -355,8 +369,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 +403,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 +465,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 +513,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 +544,6 @@
         }
         List<WidgetItem> recommendedWidgets =
                 mActivityContext.getPopupDataProvider().getRecommendedWidgets();
-        WidgetsRecommendationTableLayout table = mSearchScrollController.mRecommendedWidgetsTable;
         if (recommendedWidgets.size() > 0) {
             float noWidgetsViewHeight = 0;
             if (mIsNoWidgetsViewNeeded) {
@@ -562,9 +566,10 @@
             List<ArrayList<WidgetItem>> recommendedWidgetsInTable =
                     WidgetsTableUtils.groupWidgetItemsIntoTableWithoutReordering(
                             recommendedWidgets, mMaxSpansPerRow);
-            table.setRecommendedWidgets(recommendedWidgetsInTable, maxTableHeight);
+            mRecommendedWidgetsTable.setRecommendedWidgets(
+                    recommendedWidgetsInTable, maxTableHeight);
         } else {
-            table.setVisibility(GONE);
+            mRecommendedWidgetsTable.setVisibility(GONE);
         }
     }
 
@@ -619,10 +624,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 +667,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 +685,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 +705,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 +804,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..35fa7a4 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
@@ -19,24 +19,15 @@
 import android.content.Context;
 import android.graphics.Point;
 import android.util.AttributeSet;
+import android.util.SparseIntArray;
 import android.view.MotionEvent;
-import android.view.View;
-import android.widget.TableLayout;
 
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener;
 
-import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.FastScrollRecyclerView;
 import com.android.launcher3.R;
-import com.android.launcher3.views.ActivityContext;
-import com.android.launcher3.widget.model.WidgetListSpaceEntry;
-import com.android.launcher3.widget.model.WidgetsListBaseEntry;
-import com.android.launcher3.widget.model.WidgetsListContentEntry;
-import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
-import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
-import com.android.launcher3.widget.picker.WidgetsSpaceViewHolderBinder.EmptySpaceView;
 
 /**
  * The widgets recycler view.
@@ -51,12 +42,13 @@
     private boolean mTouchDownOnScroller;
     private HeaderViewDimensionsProvider mHeaderViewDimensionsProvider;
 
-    // Cached sizes
-    private int mLastVisibleWidgetContentTableHeight = 0;
-    private int mWidgetHeaderHeight = 0;
-    private int mWidgetEmptySpaceHeight = 0;
-
-    private final int mSpacingBetweenEntries;
+    /**
+     * There is always 1 or 0 item of VIEW_TYPE_WIDGETS_LIST. Other types have fixes sizes, so the
+     * the size can be used for all other items of same type. Caching the last know size for
+     * VIEW_TYPE_WIDGETS_LIST allows us to use it to estimate full size even when
+     * VIEW_TYPE_WIDGETS_LIST is not visible on the screen.
+     */
+    private final SparseIntArray mCachedSizes = new SparseIntArray();
 
     public WidgetsRecyclerView(Context context) {
         this(context, null);
@@ -71,13 +63,6 @@
         super(context, attrs, defStyleAttr);
         mScrollbarTop = getResources().getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
         addOnItemTouchListener(this);
-
-        ActivityContext activity = ActivityContext.lookupContext(getContext());
-        DeviceProfile grid = activity.getDeviceProfile();
-
-        // The spacing used between entries.
-        mSpacingBetweenEntries =
-                getResources().getDimensionPixelSize(R.dimen.widget_list_entry_spacing);
     }
 
     @Override
@@ -138,67 +123,6 @@
         synchronizeScrollBarThumbOffsetToViewScroll(scrollY, getAvailableScrollHeight());
     }
 
-    @Override
-    public int getCurrentScrollY() {
-        // Skip early if widgets are not bound.
-        if (isModelNotReady() || getChildCount() == 0) {
-            return -1;
-        }
-
-        int rowIndex = -1;
-        View child = null;
-
-        LayoutManager layoutManager = getLayoutManager();
-        if (layoutManager instanceof LinearLayoutManager) {
-            // Use the LayoutManager as the source of truth for visible positions. During
-            // animations, the view group child may not correspond to the visible views that appear
-            // at the top.
-            rowIndex = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();
-            child = layoutManager.findViewByPosition(rowIndex);
-        }
-
-        if (child == null) {
-            // If the layout manager returns null for any reason, which can happen before layout
-            // has occurred for the position, then look at the child of this view as a ViewGroup.
-            child = getChildAt(0);
-            rowIndex = getChildPosition(child);
-        }
-
-        for (int i = 0; i < getChildCount(); i++) {
-            View view = getChildAt(i);
-            if (view instanceof TableLayout) {
-                // This assumes there is ever only one content shown in this recycler view.
-                mLastVisibleWidgetContentTableHeight = view.getMeasuredHeight();
-            } else if (view instanceof WidgetsListHeader
-                    && mWidgetHeaderHeight == 0
-                    && view.getMeasuredHeight() > 0) {
-                // This assumes all header views are of the same height.
-                mWidgetHeaderHeight = view.getMeasuredHeight();
-            } else if (view instanceof EmptySpaceView && view.getMeasuredHeight() > 0) {
-                mWidgetEmptySpaceHeight = view.getMeasuredHeight();
-            }
-        }
-
-        int scrollPosition = getItemsHeight(rowIndex);
-        int offset = getLayoutManager().getDecoratedTop(child);
-
-        return getPaddingTop() + scrollPosition - offset;
-    }
-
-    /**
-     * Returns the available scroll height, in pixel.
-     *
-     * <p>If the recycler view can't be scrolled, returns 0.
-     */
-    @Override
-    protected int getAvailableScrollHeight() {
-        // AvailableScrollHeight = Total height of the all items - first page height
-        int firstPageHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
-        int totalHeightOfAllItems = getItemsHeight(/* untilIndex= */ mAdapter.getItemCount());
-        int availableScrollHeight = totalHeightOfAllItems - firstPageHeight;
-        return Math.max(0, availableScrollHeight);
-    }
-
     private boolean isModelNotReady() {
         return mAdapter.getItemCount() == 0;
     }
@@ -246,28 +170,28 @@
      * <p>If the untilIndex is larger than the total number of items in this adapter, returns the
      * sum of all items' height.
      */
-    private int getItemsHeight(int untilIndex) {
+    @Override
+    protected int getItemsHeight(int untilIndex) {
+        // Initialize cache
+        int childCount = getChildCount();
+        int startPosition;
+        if (childCount > 0
+                && ((startPosition = getChildAdapterPosition(getChildAt(0))) != NO_POSITION)) {
+            int loopCount = Math.min(getChildCount(), getAdapter().getItemCount() - startPosition);
+            for (int i = 0; i < loopCount; i++) {
+                mCachedSizes.put(
+                        mAdapter.getItemViewType(startPosition + i),
+                        getChildAt(i).getMeasuredHeight());
+            }
+        }
+
         if (untilIndex > mAdapter.getItems().size()) {
             untilIndex = mAdapter.getItems().size();
         }
         int totalItemsHeight = 0;
         for (int i = 0; i < untilIndex; i++) {
-            WidgetsListBaseEntry entry = mAdapter.getItems().get(i);
-            if (entry instanceof WidgetsListHeaderEntry
-                    || entry instanceof WidgetsListSearchHeaderEntry) {
-                totalItemsHeight += mWidgetHeaderHeight;
-                if (i > 0) {
-                    // Each header contains the spacing between entries as top decoration, except
-                    // the first one.
-                    totalItemsHeight += mSpacingBetweenEntries;
-                }
-            } else if (entry instanceof WidgetsListContentEntry) {
-                totalItemsHeight += mLastVisibleWidgetContentTableHeight;
-            } else if (entry instanceof WidgetListSpaceEntry) {
-                totalItemsHeight += mWidgetEmptySpaceHeight;
-            } else {
-                throw new UnsupportedOperationException("Can't estimate height for " + entry);
-            }
+            int type = mAdapter.getItemViewType(i);
+            totalItemsHeight += mCachedSizes.get(type);
         }
         return totalItemsHeight;
     }
diff --git a/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/com/android/launcher3/workprofile/PersonalWorkSlidingTabStrip.java b/src/com/android/launcher3/workprofile/PersonalWorkSlidingTabStrip.java
index 11185fb..49db2a0 100644
--- a/src/com/android/launcher3/workprofile/PersonalWorkSlidingTabStrip.java
+++ b/src/com/android/launcher3/workprofile/PersonalWorkSlidingTabStrip.java
@@ -23,7 +23,9 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.pageindicators.PageIndicator;
+import com.android.launcher3.views.ActivityContext;
 
 /**
  * Supports two indicator colors, dedicated for personal and work tabs.
@@ -72,6 +74,26 @@
         return false;
     }
 
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        if (getPaddingLeft() == 0 && getPaddingRight() == 0) {
+            // If any padding is not specified, restrict the width to emulate padding
+            int size = MeasureSpec.getSize(widthMeasureSpec);
+            size = getTabWidth(getContext(), size);
+            widthMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
+        }
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+    }
+
+    /**
+     * Returns distance between left and right app icons
+     */
+    public static int getTabWidth(Context context, int totalWidth) {
+        DeviceProfile grid = ActivityContext.lookupContext(context).getDeviceProfile();
+        int iconPadding = totalWidth / grid.numShownAllAppsColumns - grid.allAppsIconSizePx;
+        return totalWidth - iconPadding;
+    }
+
     /**
      * Interface definition for a callback to be invoked when an active page has been changed.
      */
diff --git a/tests/src/com/android/launcher3/DeviceProfileBaseTest.kt b/tests/src/com/android/launcher3/DeviceProfileBaseTest.kt
index 6d0fcb6..ba813c0 100644
--- a/tests/src/com/android/launcher3/DeviceProfileBaseTest.kt
+++ b/tests/src/com/android/launcher3/DeviceProfileBaseTest.kt
@@ -23,6 +23,8 @@
 import org.junit.Before
 import org.mockito.ArgumentMatchers.any
 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 +37,7 @@
     protected var transposeLayoutWithOrientation: Boolean = false
     protected var useTwoPanels: Boolean = false
     protected var isGestureMode: Boolean = true
+    protected var isRecentsRtl: Boolean = true
 
     @Before
     fun setUp() {
@@ -55,8 +58,9 @@
         isGestureMode
     )
 
-    protected fun initializeVarsForPhone(isLandscape: Boolean = false) {
-        val (x, y) = if (isLandscape)
+    protected fun initializeVarsForPhone(isGestureMode: Boolean = true,
+                                         isVerticalBar: Boolean = false) {
+        val (x, y) = if (isVerticalBar)
             Pair(3120, 1440)
         else
             Pair(1440, 3120)
@@ -65,11 +69,19 @@
 
         whenever(info.isTablet(any())).thenReturn(false)
         whenever(info.getDensityDpi()).thenReturn(560)
+        whenever(info.smallestSizeDp(any())).thenReturn(411f)
 
-        inv = newScalableInvariantDeviceProfile()
+        this.isGestureMode = isGestureMode
+
+        inv = newScalableInvariantDeviceProfile().apply {
+            deviceType = InvariantDeviceProfile.TYPE_PHONE
+            transposeLayoutWithOrientation = isVerticalBar
+        }
     }
 
-    protected fun initializeVarsForTablet(isLandscape: Boolean = false) {
+    protected fun initializeVarsForTablet(isLandscape: Boolean = false,
+                                          isTwoPanel: Boolean = false,
+                                          isGestureMode: Boolean = true) {
         val (x, y) = if (isLandscape)
             Pair(2560, 1600)
         else
@@ -79,8 +91,21 @@
 
         whenever(info.isTablet(any())).thenReturn(true)
         whenever(info.getDensityDpi()).thenReturn(320)
+        whenever(info.smallestSizeDp(any())).thenReturn(800f)
 
-        inv = newScalableInvariantDeviceProfile()
+        this.isGestureMode = isGestureMode
+        useTwoPanels = isTwoPanel
+
+        inv = newScalableInvariantDeviceProfile().apply {
+            deviceType = if (isTwoPanel)
+                InvariantDeviceProfile.TYPE_MULTI_DISPLAY else InvariantDeviceProfile.TYPE_TABLET
+            inlineQsb = booleanArrayOf(
+                    false,
+                    isLandscape,
+                    false,
+                    false
+            )
+        }
     }
 
     /**
@@ -110,6 +135,8 @@
             ).toTypedArray()
             hotseatBorderSpaces = FloatArray(4) { 16f }
             hotseatColumnSpan = IntArray(4) { 4 }
+            hotseatBarBottomSpace = FloatArray(4) { 48f }
+            hotseatQsbSpace = FloatArray(4) { 36f }
             iconSize = FloatArray(4) { 56f }
             allAppsIconSize = FloatArray(4) { 56f }
             iconTextSize = FloatArray(4) { 14f }
@@ -133,4 +160,12 @@
                 false
             )
         }
+
+    fun dump(dp: DeviceProfile): StringWriter {
+        val stringWriter = StringWriter()
+        val printWriter = PrintWriter(stringWriter)
+        dp.dump(context, "", printWriter)
+        printWriter.flush()
+        return stringWriter
+    }
 }
\ 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
index 593239d..5dabb33 100644
--- a/tests/src/com/android/launcher3/HotseatShownIconsTest.kt
+++ b/tests/src/com/android/launcher3/HotseatShownIconsTest.kt
@@ -32,33 +32,6 @@
 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 {
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..3ca05bc
--- /dev/null
+++ b/tests/src/com/android/launcher3/celllayout/CellLayoutBoard.java
@@ -0,0 +1,152 @@
+/*
+ * 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 {
+
+    static final int INFINITE = 99999;
+
+    char[][] mBoard = new char[30][30];
+
+    List<TestBoardWidget> mWidgetsRects = new ArrayList<>();
+    Map<Character, TestBoardWidget> mWidgetsMap = new HashMap<>();
+
+    List<TestBoardAppIcon> mIconPoints = new ArrayList<>();
+    Map<Character, TestBoardAppIcon> mIconsMap = new HashMap<>();
+
+    Point mMain = new Point();
+
+    CellLayoutBoard() {
+        for (int x = 0; x < mBoard.length; x++) {
+            for (int y = 0; y < mBoard[0].length; y++) {
+                mBoard[x][y] = '-';
+            }
+        }
+    }
+
+    public List<TestBoardWidget> getWidgets() {
+        return mWidgetsRects;
+    }
+
+    public Point getMain() {
+        return mMain;
+    }
+
+    public TestBoardWidget getWidgetRect(char c) {
+        return mWidgetsMap.get(c);
+    }
+
+    public static TestBoardWidget 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 TestBoardWidget(type, widgetRect);
+    }
+
+    public static boolean isWidget(char type) {
+        return type != 'i' && type != '-';
+    }
+
+    public static boolean isIcon(char type) {
+        return type == 'i';
+    }
+
+    private static List<TestBoardWidget> getRects(char[][] board) {
+        Set<Point> used = new HashSet<>();
+        List<TestBoardWidget> 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<TestBoardAppIcon> getIconPoints(char[][] board) {
+        List<TestBoardAppIcon> 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 TestBoardAppIcon(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 == 'm') {
+                    board.mMain = new Point(x, y);
+                }
+                if (c != '-') {
+                    board.mBoard[x][y] = line.charAt(x);
+                }
+            }
+        }
+        board.mWidgetsRects = getRects(board.mBoard);
+        board.mWidgetsRects.forEach(
+                widgetRect -> board.mWidgetsMap.put(widgetRect.mType, widgetRect));
+        board.mIconPoints = getIconPoints(board.mBoard);
+        return board;
+    }
+}
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..ed0b71d
--- /dev/null
+++ b/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java
@@ -0,0 +1,197 @@
+/*
+ * 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.WidgetUtils.createWidgetInfo;
+
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.util.Log;
+import android.view.View;
+
+import androidx.test.core.app.ApplicationProvider;
+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.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.ui.AbstractLauncherUiTest;
+import com.android.launcher3.ui.TaplTestsLauncher3;
+import com.android.launcher3.ui.TestViewHelpers;
+import com.android.launcher3.util.rule.ShellCommandRule;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
+
+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;
+
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ReorderWidgets extends AbstractLauncherUiTest {
+
+    @Rule
+    public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
+
+    private static final String TAG = ReorderWidgets.class.getSimpleName();
+
+    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 {
+        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 (TestBoardWidget widgetRect: board.getWidgets()) {
+            if (widgetRect.shouldIgnore()) {
+                continue;
+            }
+            View widget = getViewAt(widgetRect.getCellX(), widgetRect.getCellY());
+            match &= widgetRect.getSpanX()
+                    == Math.round(widget.getWidth() / (float) cellDimensions.x);
+            match &= widgetRect.getSpanY()
+                    == Math.round(widget.getHeight() / (float) cellDimensions.y);
+            if (!match) return match;
+        }
+        return match;
+    }
+
+    /**
+     * Fills the given rect in WidgetRect with 1x1 widgets. This is useful to equalize cases.
+     */
+    private void fillWithWidgets(TestBoardWidget widgetRect) {
+        int initX = widgetRect.getCellX();
+        int initY = widgetRect.getCellY();
+        for (int x = 0; x < widgetRect.getSpanX(); x++) {
+            for (int y = 0; y < widgetRect.getSpanY(); y++) {
+                int auxX = initX + x;
+                int auxY = initY + y;
+                try {
+                    // this widgets are filling, we don't care if we can't place them
+                    addWidgetInCell(
+                            new TestBoardWidget('x',
+                                    new Rect(auxX, auxY, auxX, auxY))
+                    );
+                } catch (Exception e) {
+                    Log.d(TAG, "Unable to place filling widget at " + auxX + "," + auxY);
+                }
+            }
+        }
+    }
+
+    private void addWidgetInCell(TestBoardWidget widgetRect) {
+        LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false);
+        LauncherAppWidgetInfo item = createWidgetInfo(info,
+                ApplicationProvider.getApplicationContext(), true);
+        item.cellX = widgetRect.getCellX();
+        item.cellY = widgetRect.getCellY();
+
+        item.spanX = widgetRect.getSpanX();
+        item.spanY = widgetRect.getSpanY();
+        addItemToScreen(item);
+    }
+
+    private void addCorrespondingWidgetRect(TestBoardWidget widgetRect) {
+        if (widgetRect.mType == 'x') {
+            fillWithWidgets(widgetRect);
+        } else {
+            addWidgetInCell(widgetRect);
+        }
+    }
+
+    private void runTestCase(ReorderTestCase testCase) {
+        Point mainWidgetCellPos = testCase.mStart.getMain();
+
+        testCase.mStart.getWidgets().forEach(this::addCorrespondingWidgetRect);
+
+        mLauncher.getWorkspace()
+                .getWidgetAtCell(mainWidgetCellPos.x, mainWidgetCellPos.y)
+                .dragWidgetToWorkspace(testCase.moveMainTo.x, testCase.moveMainTo.y)
+                .dismiss(); // dismiss resize frame
+
+        boolean isValid = false;
+        for (CellLayoutBoard board : testCase.mEnd) {
+            isValid |= validateBoard(board);
+        }
+        assertTrue("None 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) {
+        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));
+    }
+
+    @Test
+    public void simpleReorder() {
+        runTestCaseMap(SimpleReorderCase.TEST_BY_GRID_SIZE,
+                SimpleReorderCase.class.getSimpleName());
+    }
+
+    @Test
+    public void pushTest() {
+        runTestCaseMap(PushReorderCase.TEST_BY_GRID_SIZE, PushReorderCase.class.getSimpleName());
+    }
+
+    @Test
+    public void fullReorder() {
+        runTestCaseMap(FullReorderCase.TEST_BY_GRID_SIZE, FullReorderCase.class.getSimpleName());
+    }
+
+    @Test
+    public void moveOutReorder() {
+        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/testcases/FullReorderCase.java b/tests/src/com/android/launcher3/celllayout/testcases/FullReorderCase.java
new file mode 100644
index 0000000..ff03a50
--- /dev/null
+++ b/tests/src/com/android/launcher3/celllayout/testcases/FullReorderCase.java
@@ -0,0 +1,65 @@
+/*
+ * 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;
+
+public class FullReorderCase {
+    private static final String START_BOARD_STR_5x5 = ""
+            + "xxxxx\n"
+            + "222mm\n"
+            + "222mm\n"
+            + "ad111\n"
+            + "bc111";
+
+    private static final Point MOVE_TO_5x5 = new Point(0, 4);
+
+    private static final String END_BOARD_STR_5x5 = ""
+            + "xxxxx\n"
+            + "222ad\n"
+            + "222bc\n"
+            + "mm111\n"
+            + "mm111";
+
+    private static final ReorderTestCase TEST_CASE_5x5 = new ReorderTestCase(START_BOARD_STR_5x5,
+            MOVE_TO_5x5,
+            END_BOARD_STR_5x5);
+
+    private static final String START_BOARD_STR_6x5 = ""
+            + "xxxxxx\n"
+            + "2222mm\n"
+            + "2222mm\n"
+            + "ad1111\n"
+            + "bc1111";
+
+    private static final Point MOVE_TO_6x5 = new Point(0, 4);
+
+    private static final String END_BOARD_STR_6x5 = ""
+            + "xxxxxx\n"
+            + "2222ad\n"
+            + "2222bc\n"
+            + "mm1111\n"
+            + "mm1111";
+
+    private static final ReorderTestCase TEST_CASE_6x5 = new ReorderTestCase(START_BOARD_STR_6x5,
+            MOVE_TO_6x5,
+            END_BOARD_STR_6x5);
+
+    public static final Map<Point, ReorderTestCase> TEST_BY_GRID_SIZE =
+            Map.of(new Point(5, 5), TEST_CASE_5x5, new Point(6, 5), TEST_CASE_6x5);
+}
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..32bf05a
--- /dev/null
+++ b/tests/src/com/android/launcher3/celllayout/testcases/MoveOutReorderCase.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.celllayout.testcases;
+
+import android.graphics.Point;
+
+import java.util.Map;
+
+public class MoveOutReorderCase {
+    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);
+
+
+    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);
+
+    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);
+}
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..b386946
--- /dev/null
+++ b/tests/src/com/android/launcher3/celllayout/testcases/PushReorderCase.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.testcases;
+
+import android.graphics.Point;
+
+import com.android.launcher3.celllayout.CellLayoutBoard;
+
+import java.util.Map;
+
+public class PushReorderCase {
+    private static final String START_BOARD_STR_5x5 = ""
+            + "xxxxx\n"
+            + "222m-\n"
+            + "--111\n"
+            + "--333\n"
+            + "-----";
+    private static final CellLayoutBoard START_BOARD_5x5 = CellLayoutBoard.boardFromString(
+            START_BOARD_STR_5x5);
+
+    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 CellLayoutBoard END_BOARD_5x5 = CellLayoutBoard.boardFromString(
+            END_BOARD_STR_5x5);
+
+    private static final ReorderTestCase TEST_CASE_5x5 = new ReorderTestCase(START_BOARD_5x5,
+            MOVE_TO_5x5,
+            END_BOARD_5x5);
+
+
+    private static final String START_BOARD_STR_6x5 = ""
+            + "xxxxxx\n"
+            + "2222m-\n"
+            + "--111-\n"
+            + "--333-\n"
+            + "------";
+    private static final CellLayoutBoard START_BOARD_6x5 = CellLayoutBoard.boardFromString(
+            START_BOARD_STR_6x5);
+
+    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 CellLayoutBoard END_BOARD_6x5 = CellLayoutBoard.boardFromString(
+            END_BOARD_STR_6x5);
+
+    private static final ReorderTestCase TEST_CASE_6x5 = new ReorderTestCase(START_BOARD_6x5,
+            MOVE_TO_6x5,
+            END_BOARD_6x5);
+
+    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);
+}
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..57e1398
--- /dev/null
+++ b/tests/src/com/android/launcher3/celllayout/testcases/SimpleReorderCase.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.celllayout.testcases;
+
+import android.graphics.Point;
+
+import com.android.launcher3.celllayout.CellLayoutBoard;
+
+import java.util.Map;
+
+public class SimpleReorderCase {
+    private static final String START_BOARD_STR = ""
+            + "xxxxx\n"
+            + "--mm-\n"
+            + "--mm-\n"
+            + "-----\n"
+            + "-----";
+    private static final CellLayoutBoard START_BOARD_5x5 = CellLayoutBoard.boardFromString(
+            START_BOARD_STR);
+
+    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 CellLayoutBoard END_BOARD_5x5 = CellLayoutBoard.boardFromString(
+            END_BOARD_STR_5x5);
+
+    private static final ReorderTestCase TEST_CASE_5x5 = new ReorderTestCase(START_BOARD_5x5,
+            MOVE_TO_5x5,
+            END_BOARD_5x5);
+
+    public static final Map<Point, ReorderTestCase> TEST_BY_GRID_SIZE =
+            Map.of(new Point(5, 5), TEST_CASE_5x5);
+}
diff --git a/tests/src/com/android/launcher3/deviceemulator/DisplayEmulator.java b/tests/src/com/android/launcher3/deviceemulator/DisplayEmulator.java
index 31468c5..e2ed65f 100644
--- a/tests/src/com/android/launcher3/deviceemulator/DisplayEmulator.java
+++ b/tests/src/com/android/launcher3/deviceemulator/DisplayEmulator.java
@@ -47,8 +47,7 @@
      * By changing the WindowManagerProxy we can override the window insets information
      **/
     private IWindowManager changeWindowManagerInstance(DeviceEmulationData deviceData) {
-        WindowManagerProxy.INSTANCE.initializeForTesting(
-                new TestWindowManagerProxy(mContext, deviceData));
+        WindowManagerProxy.INSTANCE.initializeForTesting(new TestWindowManagerProxy(deviceData));
         return WindowManagerGlobal.getWindowManagerService();
     }
 
@@ -57,8 +56,7 @@
         WindowManagerProxy original = WindowManagerProxy.INSTANCE.get(mContext);
         // Set up emulation
         final int userId = UserHandle.myUserId();
-        WindowManagerProxy.INSTANCE.initializeForTesting(
-                new TestWindowManagerProxy(mContext, device));
+        WindowManagerProxy.INSTANCE.initializeForTesting(new TestWindowManagerProxy(device));
         IWindowManager wm = changeWindowManagerInstance(device);
         // Change density twice to force display controller to reset its state
         wm.setForcedDisplayDensityForUser(Display.DEFAULT_DISPLAY, device.density / 2, userId);
diff --git a/tests/src/com/android/launcher3/deviceemulator/TestWindowManagerProxy.java b/tests/src/com/android/launcher3/deviceemulator/TestWindowManagerProxy.java
index cbea688..2d6bbcc 100644
--- a/tests/src/com/android/launcher3/deviceemulator/TestWindowManagerProxy.java
+++ b/tests/src/com/android/launcher3/deviceemulator/TestWindowManagerProxy.java
@@ -19,7 +19,6 @@
 import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.view.Display;
 import android.view.WindowInsets;
 
 import com.android.launcher3.deviceemulator.models.DeviceEmulationData;
@@ -32,17 +31,12 @@
 
     private final DeviceEmulationData mDevice;
 
-    public TestWindowManagerProxy(Context context, DeviceEmulationData device) {
+    public TestWindowManagerProxy(DeviceEmulationData device) {
         super(true);
         mDevice = device;
     }
 
     @Override
-    public boolean isInternalDisplay(Display display) {
-        return display.getDisplayId() == Display.DEFAULT_DISPLAY;
-    }
-
-    @Override
     protected int getDimenByName(Resources res, String resName) {
         Integer mock = mDevice.resourceOverrides.get(resName);
         return mock != null ? mock : super.getDimenByName(res, resName);
@@ -54,27 +48,25 @@
     }
 
     @Override
-    public CachedDisplayInfo getDisplayInfo(Context context, Display display) {
-        int rotation = display.getRotation();
+    public CachedDisplayInfo getDisplayInfo(Context displayInfoContext) {
+        int rotation = getRotation(displayInfoContext);
         Point size = new Point(mDevice.width, mDevice.height);
         RotationUtils.rotateSize(size, rotation);
         Rect cutout = new Rect(mDevice.cutout);
         RotationUtils.rotateRect(cutout, rotation);
-        return new CachedDisplayInfo(getDisplayId(display), size, rotation, cutout);
+        return new CachedDisplayInfo(size, rotation, cutout);
     }
 
     @Override
-    public WindowBounds getRealBounds(Context windowContext, Display display,
-            CachedDisplayInfo info) {
-        return estimateInternalDisplayBounds(windowContext)
-                .get(getDisplayId(display)).second[display.getRotation()];
+    public WindowBounds getRealBounds(Context displayInfoContext, CachedDisplayInfo info) {
+        return estimateInternalDisplayBounds(displayInfoContext).get(
+                getDisplayInfo(displayInfoContext))[getDisplay(displayInfoContext).getRotation()];
     }
 
     @Override
     public WindowInsets normalizeWindowInsets(Context context, WindowInsets oldInsets,
             Rect outInsets) {
-        outInsets.set(getRealBounds(context, context.getDisplay(),
-                getDisplayInfo(context, context.getDisplay())).insets);
+        outInsets.set(getRealBounds(context, getDisplayInfo(context)).insets);
         return oldInsets;
     }
 }
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 6f8b9d2..1f6e1ec 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -563,10 +563,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 +635,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/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 8a97c6b..03bf4af 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -25,6 +25,7 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import android.content.Intent;
 import android.graphics.Point;
@@ -53,7 +54,7 @@
 import com.android.launcher3.widget.picker.WidgetsRecyclerView;
 
 import org.junit.Before;
-import org.junit.Rule;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -192,6 +193,17 @@
     }
 
     @Test
+    @PortraitLandscape
+    public void testAllAppsDeadzoneForTablet() throws Exception {
+        assumeTrue(mLauncher.isTablet());
+
+        mLauncher.getWorkspace().switchToAllApps().dismissByTappingOutsideForTablet(
+                true /* tapRight */);
+        mLauncher.getWorkspace().switchToAllApps().dismissByTappingOutsideForTablet(
+                false /* tapRight */);
+    }
+
+    @Test
     @ScreenRecord // b/202433017
     public void testWorkspace() throws Exception {
         final Workspace workspace = mLauncher.getWorkspace();
@@ -199,8 +211,8 @@
         // Test that ensureWorkspaceIsScrollable adds a page by dragging an icon there.
         executeOnLauncher(launcher -> assertFalse("Initial workspace state is scrollable",
                 isWorkspaceScrollable(launcher)));
-        assertNull("Chrome app was found on empty workspace",
-                workspace.tryGetWorkspaceAppIcon("Chrome"));
+        workspace.verifyWorkspaceAppIconIsGone(
+                "Chrome app was found on empty workspace", "Chrome");
 
         workspace.ensureWorkspaceIsScrollable();
 
@@ -376,6 +388,8 @@
 
     @Test
     @PortraitLandscape
+    @ScreenRecord
+    @Ignore // b/233075289
     public void testDragToFolder() {
         // TODO: add the use case to drag an icon to an existing folder. Currently it either fails
         // on tablets or phones due to difference in resolution.
@@ -388,10 +402,10 @@
         folder.getAppIcon(GMAIL_APP_NAME);
         Workspace workspace = folder.close();
 
-        assertNull(STORE_APP_NAME + " should be moved to a folder.",
-                workspace.tryGetWorkspaceAppIcon(STORE_APP_NAME));
-        assertNull(GMAIL_APP_NAME + " should be moved to a folder.",
-                workspace.tryGetWorkspaceAppIcon(GMAIL_APP_NAME));
+        workspace.verifyWorkspaceAppIconIsGone(STORE_APP_NAME + " should be moved to a folder.",
+                STORE_APP_NAME);
+        workspace.verifyWorkspaceAppIconIsGone(GMAIL_APP_NAME + " should be moved to a folder.",
+                GMAIL_APP_NAME);
 
         final HomeAppIcon mapIcon = createShortcutInCenterIfNotExist(MAPS_APP_NAME);
         folderIcon = mapIcon.dragToIcon(folderIcon);
@@ -399,8 +413,8 @@
         folder.getAppIcon(MAPS_APP_NAME);
         workspace = folder.close();
 
-        assertNull(MAPS_APP_NAME + " should be moved to a folder.",
-                workspace.tryGetWorkspaceAppIcon(MAPS_APP_NAME));
+        workspace.verifyWorkspaceAppIconIsGone(MAPS_APP_NAME + " should be moved to a folder.",
+                MAPS_APP_NAME);
     }
 
     @Test
@@ -424,8 +438,9 @@
         for (String appName : new String[]{"Gmail", "Play Store", APP_NAME}) {
             final HomeAppIcon homeAppIcon = createShortcutInCenterIfNotExist(appName);
             Workspace workspace = mLauncher.getWorkspace().deleteAppIcon(homeAppIcon);
-            assertNull(appName + " app was found after being deleted from workspace",
-                    workspace.tryGetWorkspaceAppIcon(appName));
+            workspace.verifyWorkspaceAppIconIsGone(
+                    appName + " app was found after being deleted from workspace",
+                    appName);
         }
     }
 
@@ -509,10 +524,8 @@
                     .containsAtLeast(DUMMY_APP_NAME, MAPS_APP_NAME, STORE_APP_NAME);
 
             mLauncher.getWorkspace().getWorkspaceAppIcon(DUMMY_APP_NAME).uninstall();
-
-            assertNull(
-                    DUMMY_APP_NAME + " app was found after being uninstalled",
-                    mLauncher.getWorkspace().tryGetWorkspaceAppIcon(DUMMY_APP_NAME));
+            mLauncher.getWorkspace().verifyWorkspaceAppIconIsGone(
+                    DUMMY_APP_NAME + " was expected to disappear after uninstall.", DUMMY_APP_NAME);
 
             Map<String, Point> finalPositions =
                     mLauncher.getWorkspace().getWorkspaceIconsPositions();
@@ -522,6 +535,37 @@
         }
     }
 
+    @Test
+    @PortraitLandscape
+    public void testDragShortcutToWorkspaceCell() throws Exception {
+        Point[] targets = getCornersAndCenterPositions();
+
+        for (Point target : targets) {
+            final HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
+            allApps.freeze();
+            try {
+                allApps.getAppIcon(APP_NAME)
+                        .openDeepShortcutMenu()
+                        .getMenuItem(0)
+                        .dragToWorkspace(target.x, target.y);
+            } finally {
+                allApps.unfreeze();
+            }
+        }
+    }
+
+    @Test
+    @PortraitLandscape
+    public void testAddDeleteShortcutOnHotseat() {
+        mLauncher.getWorkspace()
+                .deleteAppIcon(mLauncher.getWorkspace().getHotseatAppIcon(0))
+                .switchToAllApps()
+                .getAppIcon(APP_NAME)
+                .dragToHotseat(0);
+        mLauncher.getWorkspace().deleteAppIcon(
+                mLauncher.getWorkspace().getHotseatAppIcon(APP_NAME));
+    }
+
     /**
      * @return List of workspace grid coordinates. Those are not pixels. See {@link
      *     Workspace#getIconGridDimensions()}
diff --git a/tests/src/com/android/launcher3/ui/WorkProfileTest.java b/tests/src/com/android/launcher3/ui/WorkProfileTest.java
index 35b4ca6..a7a17b1 100644
--- a/tests/src/com/android/launcher3/ui/WorkProfileTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkProfileTest.java
@@ -30,7 +30,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;
@@ -155,7 +154,7 @@
     public void testEdu() {
         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();
         });
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/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index b7bca02..6a11336 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.
      */
diff --git a/tests/tapl/com/android/launcher3/tapl/Folder.java b/tests/tapl/com/android/launcher3/tapl/Folder.java
index 26f0a8b..1352cc0 100644
--- a/tests/tapl/com/android/launcher3/tapl/Folder.java
+++ b/tests/tapl/com/android/launcher3/tapl/Folder.java
@@ -16,11 +16,6 @@
 
 package com.android.launcher3.tapl;
 
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.os.SystemClock;
-import android.view.MotionEvent;
-
 import androidx.annotation.NonNull;
 import androidx.test.uiautomator.UiObject2;
 
@@ -50,25 +45,15 @@
         }
     }
 
-    private void touchOutsideFolder() {
-        Rect containerBounds = mLauncher.getVisibleBounds(this.mContainer);
-        final long downTime = SystemClock.uptimeMillis();
-        Point containerLeftTopCorner = new Point(containerBounds.left - 1, containerBounds.top - 1);
-        mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN,
-                containerLeftTopCorner, LauncherInstrumentation.GestureScope.INSIDE);
-        mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_UP,
-                containerLeftTopCorner, LauncherInstrumentation.GestureScope.INSIDE);
-    }
-
     /**
-     * CLose opened folder if possible. It throws assertion error if the folder is already closed.
+     * Close opened folder if possible. It throws assertion error if the folder is already closed.
      */
     public Workspace close() {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                      "Want to close opened folder")) {
             mLauncher.waitForLauncherObject(FOLDER_CONTENT_RES_ID);
-            touchOutsideFolder();
+            mLauncher.touchOutsideContainer(this.mContainer, false /* tapRight */);
             mLauncher.waitUntilLauncherObjectGone(FOLDER_CONTENT_RES_ID);
             return mLauncher.getWorkspace();
         }
diff --git a/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java b/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java
index c275f3b..7123de4 100644
--- a/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java
@@ -19,6 +19,7 @@
 import androidx.test.uiautomator.UiObject2;
 
 public class HomeAllApps extends AllApps {
+    private static final String BOTTOM_SHEET_RES_ID = "bottom_sheet_background";
 
     HomeAllApps(LauncherInstrumentation launcher) {
         super(launcher);
@@ -45,4 +46,23 @@
     protected boolean hasSearchBox() {
         return true;
     }
+
+    /**
+     * Taps outside bottom sheet to dismiss and return to workspace. Available on tablets only.
+     * @param tapRight Tap on the right of bottom sheet if true, or left otherwise.
+     */
+    public Workspace dismissByTappingOutsideForTablet(boolean tapRight) {
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+             LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                     "want to tap outside AllApps bottom sheet on the "
+                             + (tapRight ? "right" : "left"))) {
+            final UiObject2 allAppsBottomSheet =
+                    mLauncher.waitForLauncherObject(BOTTOM_SHEET_RES_ID);
+            mLauncher.touchOutsideContainer(allAppsBottomSheet, tapRight);
+            try (LauncherInstrumentation.Closable tapped = mLauncher.addContextLayer(
+                    "tapped outside AllApps bottom sheet")) {
+                return mLauncher.getWorkspace();
+            }
+        }
+    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/HomeAppIcon.java b/tests/tapl/com/android/launcher3/tapl/HomeAppIcon.java
index 71d8ba9..693baa0 100644
--- a/tests/tapl/com/android/launcher3/tapl/HomeAppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/HomeAppIcon.java
@@ -103,40 +103,45 @@
     }
 
     /**
-     * Drag an object to the given cell in workspace. The target cell must be empty.
+     * Drag an object to the given cell in hotseat. The target cell should be expected to be empty.
      *
-     * @param cellX zero based column number, starting from the left of the screen.
-     * @param cellY zero based row number, starting from the top of the screen.
+     * @param cellInd zero based index number of the hotseat cells.
+     * @return the workspace app icon.
      */
-    public HomeAppIcon dragToWorkspace(int cellX, int cellY) {
+    @NonNull
+    public WorkspaceAppIcon dragToHotseat(int cellInd) {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
-                     String.format("want to drag the icon to cell(%d, %d)", cellX, cellY))
+                     String.format("want to drag the icon to hotseat cell %d", cellInd))
         ) {
-            final Supplier<Point> dest = () -> Workspace.getCellCenter(mLauncher, cellX, cellY);
-            Workspace.dragIconToWorkspace(
+            final Supplier<Point> dest = () -> Workspace.getHotseatCellCenter(mLauncher, cellInd);
+
+            Workspace.dragIconToHotseat(
                     mLauncher,
-                    /* launchable= */ this,
+                    this,
                     dest,
                     () -> addExpectedEventsForLongClick(),
                     /*expectDropEvents= */ null);
             try (LauncherInstrumentation.Closable ignore = mLauncher.addContextLayer("dragged")) {
                 WorkspaceAppIcon appIcon =
-                        (WorkspaceAppIcon) mLauncher.getWorkspace().getWorkspaceAppIcon(mAppName);
+                        (WorkspaceAppIcon) mLauncher.getWorkspace().getHotseatAppIcon(mAppName);
                 mLauncher.assertTrue(
-                        String.format(
-                                "The %s icon should be in the cell (%d, %d).", mAppName, cellX,
-                                cellY),
-                        appIcon.isInCell(cellX, cellY));
+                        String.format("The %s icon should be in the hotseat cell %d.", mAppName,
+                                cellInd),
+                        appIcon.isInHotseatCell(cellInd));
                 return appIcon;
             }
         }
     }
 
-
     /** This method requires public access, however should not be called in tests. */
     @Override
     public Launchable getLaunchable() {
         return this;
     }
+
+    boolean isInHotseatCell(int cellInd) {
+        final Point center = Workspace.getHotseatCellCenter(mLauncher, cellInd);
+        return mObject.getVisibleBounds().contains(center.x, center.y);
+    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 9d25b1b..5f92097 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -84,7 +84,6 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Optional;
-import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.function.Consumer;
 import java.util.function.Function;
@@ -101,7 +100,6 @@
     private static final String TAG = "Tapl";
     private static final int ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME = 15;
     private static final int GESTURE_STEP_MS = 16;
-    private static final long FORCE_PAUSE_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(2);
 
     static final Pattern EVENT_TOUCH_DOWN = getTouchEventPattern("ACTION_DOWN");
     static final Pattern EVENT_TOUCH_UP = getTouchEventPattern("ACTION_UP");
@@ -123,8 +121,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}
@@ -171,7 +169,7 @@
     private static final String WIDGETS_RES_ID = "primary_widgets_list_view";
     private static final String CONTEXT_MENU_RES_ID = "popup_container";
     private static final String TASKBAR_RES_ID = "taskbar_view";
-    public static final int WAIT_TIME_MS = 60000;
+    public static final int WAIT_TIME_MS = 30000;
     private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
     private static final String ANDROID_PACKAGE = "android";
 
@@ -362,10 +360,6 @@
         return getRealDisplaySize().x / 2f;
     }
 
-    private void setForcePauseTimeout(long timeout) {
-        getTestInfo(TestProtocol.REQUEST_SET_FORCE_PAUSE_TIMEOUT, Long.toString(timeout));
-    }
-
     public void setEnableRotation(boolean on) {
         getTestInfo(TestProtocol.REQUEST_ENABLE_ROTATION, Boolean.toString(on));
     }
@@ -379,6 +373,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 {
@@ -739,7 +744,8 @@
 
                     return waitForLauncherObject(APPS_RES_ID);
                 }
-                case OVERVIEW: {
+                case OVERVIEW:
+                case SPLIT_SCREEN_SELECT: {
                     waitUntilLauncherObjectGone(APPS_RES_ID);
                     waitUntilLauncherObjectGone(WORKSPACE_RES_ID);
                     waitUntilLauncherObjectGone(WIDGETS_RES_ID);
@@ -886,7 +892,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
@@ -1087,6 +1092,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(
@@ -1217,6 +1230,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);
@@ -1831,4 +1851,26 @@
         return ResourceUtils.getBoolByName(
                 "config_supportsRoundedCornersOnWindows", resources, false);
     }
+
+    /**
+     * Taps outside container to dismiss.
+     * @param container container to be dismissed
+     * @param tapRight tap on the right of the container if true, or left otherwise
+     */
+    void touchOutsideContainer(UiObject2 container, boolean tapRight) {
+        try (LauncherInstrumentation.Closable c = addContextLayer(
+                "want to tap outside container on the " + (tapRight ? "right" : "left"))) {
+            Rect containerBounds = getVisibleBounds(container);
+            final long downTime = SystemClock.uptimeMillis();
+            final Point tapTarget = new Point(
+                    tapRight
+                            ? (containerBounds.right + getRealDisplaySize().x) / 2
+                            : containerBounds.left / 2,
+                    containerBounds.top + 1);
+            sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, tapTarget,
+                    LauncherInstrumentation.GestureScope.INSIDE);
+            sendPointer(downTime, downTime, MotionEvent.ACTION_UP, tapTarget,
+                    LauncherInstrumentation.GestureScope.INSIDE);
+        }
+    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/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..5710713 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewActions.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewActions.java
@@ -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..ac7424e 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -20,6 +20,8 @@
 
 import android.graphics.Rect;
 
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
 import androidx.test.uiautomator.UiObject2;
 
 import com.android.launcher3.testing.TestProtocol;
@@ -32,8 +34,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;
@@ -125,7 +131,7 @@
     }
 
     /**
-     * Clicks at the task.
+     * Clicks the task.
      */
     public LaunchedAppState open() {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
@@ -136,8 +142,21 @@
                     () -> "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);
+            }
         }
     }
 }
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..3cf3ed6
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/SplitScreenSelect.java
@@ -0,0 +1,35 @@
+/*
+ * 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;
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/Widget.java b/tests/tapl/com/android/launcher3/tapl/Widget.java
index 2346249..e1a09af 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widget.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widget.java
@@ -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/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index eb7f05b..6044b06 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -39,6 +39,7 @@
 import androidx.test.uiautomator.UiObject2;
 import androidx.test.uiautomator.Until;
 
+import com.android.launcher3.testing.HotseatCellCenterRequest;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.testing.WorkspaceCellCenterRequest;
 
@@ -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.
@@ -214,10 +229,11 @@
     private void dragIcon(UiObject2 workspace, HomeAppIcon homeAppIcon, int pageDelta) {
         int pageWidth = mLauncher.getDevice().getDisplayWidth() / pagesPerScreen();
         int targetX = (pageWidth / 2) + pageWidth * pageDelta;
+        int targetY = mLauncher.getVisibleBounds(workspace).centerY();
         dragIconToWorkspace(
                 mLauncher,
                 homeAppIcon,
-                new Point(targetX, mLauncher.getVisibleBounds(workspace).centerY()),
+                () -> new Point(targetX, targetY),
                 false,
                 false,
                 () -> mLauncher.expectEvent(
@@ -236,8 +252,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 +284,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 +380,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 +412,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 +420,7 @@
                     LauncherInstrumentation.EVENT_START);
         }
         dragIconToWorkspace(
-                launcher, launchable, () -> dest, expectLongClickEvents, expectDropEvents);
+                launcher, launchable, dest, expectLongClickEvents, expectDropEvents);
     }
 
     /**
@@ -456,6 +496,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 +582,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;
+            }
         }
     }