Merge "Revert "Revert "Fix the badge in badge (triple badging), by simp..."" into main
diff --git a/Android.bp b/Android.bp
index 010ea45..e2dd48d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -23,42 +23,66 @@
 // All sources are split so they can be reused in many other libraries/apps in other folders
 filegroup {
     name: "launcher-src",
-    srcs: [ "src/**/*.java", "src/**/*.kt" ],
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
 }
 
 filegroup {
     name: "launcher-quickstep-src",
-    srcs: [ "quickstep/src/**/*.java", "quickstep/src/**/*.kt" ],
+    srcs: [
+        "quickstep/src/**/*.java",
+        "quickstep/src/**/*.kt",
+    ],
 }
 
 filegroup {
     name: "launcher-go-src",
-    srcs: [ "go/src/**/*.java", "go/src/**/*.kt" ],
+    srcs: [
+        "go/src/**/*.java",
+        "go/src/**/*.kt",
+    ],
 }
 
 filegroup {
     name: "launcher-go-quickstep-src",
-    srcs: [ "go/quickstep/src/**/*.java", "go/quickstep/src/**/*.kt" ],
+    srcs: [
+        "go/quickstep/src/**/*.java",
+        "go/quickstep/src/**/*.kt",
+    ],
 }
 
 filegroup {
     name: "launcher-src_shortcuts_overrides",
-    srcs: [ "src_shortcuts_overrides/**/*.java", "src_shortcuts_overrides/**/*.kt" ],
+    srcs: [
+        "src_shortcuts_overrides/**/*.java",
+        "src_shortcuts_overrides/**/*.kt",
+    ],
 }
 
 filegroup {
     name: "launcher-src_ui_overrides",
-    srcs: [ "src_ui_overrides/**/*.java", "src_ui_overrides/**/*.kt" ],
+    srcs: [
+        "src_ui_overrides/**/*.java",
+        "src_ui_overrides/**/*.kt",
+    ],
 }
 
 filegroup {
     name: "launcher-ext_tests",
-    srcs: [ "ext_tests/**/*.java", "ext_tests/**/*.kt" ],
+    srcs: [
+        "ext_tests/**/*.java",
+        "ext_tests/**/*.kt",
+    ],
 }
 
 filegroup {
     name: "launcher-quickstep-ext_tests",
-    srcs: [ "quickstep/ext_tests/**/*.java", "quickstep/ext_tests/**/*.kt" ],
+    srcs: [
+        "quickstep/ext_tests/**/*.java",
+        "quickstep/ext_tests/**/*.kt",
+    ],
 }
 
 // Proguard files for Launcher3
@@ -85,7 +109,7 @@
     srcs: [
         "tests/tapl/**/*.java",
     ],
-    resource_dirs: [ ],
+    resource_dirs: [],
     manifest: "tests/tapl/AndroidManifest.xml",
     platform_apis: true,
 }
@@ -99,7 +123,7 @@
     sdk_version: "current",
     proto: {
         type: "lite",
-        local_include_dirs:[
+        local_include_dirs: [
             "protos",
             "protos_overrides",
         ],
@@ -115,14 +139,14 @@
     sdk_version: "current",
     proto: {
         type: "lite",
-        local_include_dirs:[
+        local_include_dirs: [
             "quickstep/protos_overrides",
         ],
     },
     static_libs: [
-      "libprotobuf-java-lite",
-      "launcher_log_protos_lite"
-      ],
+        "libprotobuf-java-lite",
+        "launcher_log_protos_lite",
+    ],
 }
 
 java_library {
@@ -139,7 +163,7 @@
 // Library with all the dependencies for building Launcher3
 android_library {
     name: "Launcher3ResLib",
-    srcs: [ ],
+    srcs: [],
     resource_dirs: ["res"],
     static_libs: [
         "LauncherPluginLib",
@@ -154,13 +178,13 @@
         "com.google.android.material_material",
         "iconloader_base",
         "view_capture",
-        "animationlib"
+        "animationlib",
     ],
     manifest: "AndroidManifest-common.xml",
     sdk_version: "current",
     min_sdk_version: min_launcher3_sdk_version,
     lint: {
-        baseline_filename: "lint-baseline-res-lib.xml",
+        baseline_filename: "lint-baseline2.xml",
     },
 }
 
@@ -182,7 +206,7 @@
     min_sdk_version: min_launcher3_sdk_version,
     manifest: "AndroidManifest-common.xml",
     lint: {
-        baseline_filename: "lint-baseline-common-deps-lib.xml",
+        baseline_filename: "lint-baseline2.xml",
     },
 }
 
@@ -229,14 +253,14 @@
         "AndroidManifest-common.xml",
     ],
     lint: {
-        baseline_filename: "lint-baseline-launcher3.xml",
+        baseline_filename: "lint-baseline.xml",
     },
 }
 
 // Library with all the dependencies for building quickstep
 android_library {
     name: "QuickstepResLib",
-    srcs: [ ],
+    srcs: [],
     resource_dirs: [
         "quickstep/res",
     ],
@@ -255,7 +279,6 @@
     min_sdk_version: "current",
 }
 
-
 // Library with all the dependencies for building Launcher Go
 android_library {
     name: "LauncherGoResLib",
@@ -285,9 +308,6 @@
         "AndroidManifest-common.xml",
     ],
     min_sdk_version: "current",
-    lint: {
-        baseline_filename: "lint-baseline-go-res-lib.xml",
-    },
 }
 
 // Build rule for Quickstep library
@@ -316,9 +336,6 @@
     manifest: "quickstep/AndroidManifest.xml",
     platform_apis: true,
     min_sdk_version: "current",
-    lint: {
-        baseline_filename: "lint-baseline-launcher3.xml",
-    },
 }
 
 // Build rule for Launcher3 Go app for Android Go devices.
@@ -360,7 +377,7 @@
     manifest: "go/AndroidManifest.xml",
     jacoco: {
         include_filter: ["com.android.launcher3.*"],
-    }
+    },
 
 }
 
@@ -396,7 +413,7 @@
     manifest: "quickstep/AndroidManifest.xml",
     jacoco: {
         include_filter: ["com.android.launcher3.*"],
-    }
+    },
 
 }
 
@@ -414,7 +431,7 @@
     min_sdk_version: "current",
     target_sdk_version: "current",
 
-    srcs: [ ],
+    srcs: [],
 
     resource_dirs: [
         "go/quickstep/res",
@@ -446,7 +463,6 @@
     manifest: "quickstep/AndroidManifest.xml",
     jacoco: {
         include_filter: ["com.android.launcher3.*"],
-    }
+    },
 
 }
-
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 210cfd0..a62f809 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -67,14 +67,7 @@
     name: "enable_taskbar_pinning"
     namespace: "launcher"
     description: "Enables taskbar pinning to allow user to switch between transient and persistent taskbar flavors."
-    bug: "270396583"
-}
-
-flag {
-    name: "enable_split_from_fullscreen_with_keyboard_shortcuts"
-    namespace: "launcher"
-    description: "Enables initiating split from a fullscreen app using keyboard shortcuts"
-    bug: "270394122"
+    bug: "296231746"
 }
 
 flag {
@@ -97,3 +90,10 @@
     description: "Enables full width two pane widget picker for tablets in landscape and portrait"
     bug: "315055849"
 }
+
+flag {
+    name: "enable_support_for_archiving"
+    namespace: "launcher"
+    description: "Enables support for archived apps in Launcher3, such as empty progress bar etc."
+    bug: "210590852"
+}
diff --git a/aconfig/launcher_search.aconfig b/aconfig/launcher_search.aconfig
index fc79200..97e56b7 100644
--- a/aconfig/launcher_search.aconfig
+++ b/aconfig/launcher_search.aconfig
@@ -12,4 +12,18 @@
     namespace: "launcher_search"
     description: "This flag enables the animation of the Private Space container"
     bug: "299294792"
+}
+
+flag {
+    name: "private_space_sys_apps_separation"
+    namespace: "launcher_search"
+    description: "This flag enables showing system apps separate in Private Space container."
+    bug: "308054233"
+}
+
+flag {
+    name: "private_space_app_installer_button"
+    namespace: "launcher_search"
+    description: "This flag enables addition of App Installer button in Private Space container."
+    bug: "308064949"
 }
\ No newline at end of file
diff --git a/go/quickstep/src/com/android/launcher3/AppSharing.java b/go/quickstep/src/com/android/launcher3/AppSharing.java
index cb1f1c7..78524d1 100644
--- a/go/quickstep/src/com/android/launcher3/AppSharing.java
+++ b/go/quickstep/src/com/android/launcher3/AppSharing.java
@@ -27,6 +27,7 @@
 import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
@@ -64,14 +65,17 @@
 
     private static final String TAG = "AppSharing";
     private static final String FILE_PROVIDER_SUFFIX = ".overview.fileprovider";
-    private static final String APP_EXSTENSION = ".apk";
+    private static final String APP_EXTENSION = ".apk";
     private static final String APP_MIME_TYPE = "application/application";
 
     private final String mSharingComponent;
     private AppShareabilityManager mShareabilityMgr;
 
     private AppSharing(Launcher launcher) {
-        mSharingComponent = launcher.getText(R.string.app_sharing_component).toString();
+        String sharingComponent = Settings.Secure.getString(launcher.getContentResolver(),
+                Settings.Secure.NEARBY_SHARING_COMPONENT);
+        mSharingComponent = TextUtils.isEmpty(sharingComponent) ? launcher.getText(
+                R.string.app_sharing_component).toString() : sharingComponent;
     }
 
     private Uri getShareableUri(Context context, String path, String displayName) {
@@ -147,7 +151,7 @@
                 PackageInfo packageInfo = packageManager.getPackageInfo(packageName, 0);
                 sourceDir = packageInfo.applicationInfo.sourceDir;
                 appLabel = packageManager.getApplicationLabel(packageInfo.applicationInfo)
-                        .toString() + APP_EXSTENSION;
+                        + APP_EXTENSION;
             } catch (Exception e) {
                 Log.e(TAG, "Could not find info for package \"" + packageName + "\"");
                 return;
@@ -175,9 +179,7 @@
                 return;
             }
             checkShareability(/* requestUpdateIfUnknown */ false);
-            mTarget.runOnUiThread(() -> {
-                mPopupDataProvider.redrawSystemShortcuts();
-            });
+            mTarget.runOnUiThread(mPopupDataProvider::redrawSystemShortcuts);
         }
 
         private void checkShareability(boolean requestUpdateIfUnknown) {
diff --git a/lint-baseline-common-deps-lib.xml b/lint-baseline-common-deps-lib.xml
deleted file mode 100644
index e52f8fb..0000000
--- a/lint-baseline-common-deps-lib.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">
-
-    <issue
-        id="NewApi"
-        message="`?android:attr/dialogCornerRadius` requires API level 28 (current min is 26)"
-        errorLine1="        android:topLeftRadius=&quot;?android:attr/dialogCornerRadius&quot;"
-        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/res/drawable/add_item_dialog_background.xml"
-            line="6"
-            column="9"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="`?android:attr/dialogCornerRadius` requires API level 28 (current min is 26)"
-        errorLine1="        android:topRightRadius=&quot;?android:attr/dialogCornerRadius&quot; />"
-        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/res/drawable/add_item_dialog_background.xml"
-            line="7"
-            column="9"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="`@android:style/Widget.DeviceDefault.Button.Colored` requires API level 28 (current min is 26)"
-        errorLine1="    &lt;style name=&quot;Widget.DeviceDefault.Button.Rounded.Colored&quot; parent=&quot;@android:style/Widget.DeviceDefault.Button.Colored&quot;>"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/res/values/styles.xml"
-            line="287"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="`@android:dimen/system_app_widget_background_radius` requires API level 31 (current min is 26)"
-        errorLine1="    &lt;corners android:radius=&quot;@android:dimen/system_app_widget_background_radius&quot; />"
-        errorLine2="             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/res/drawable/widget_resize_frame.xml"
-            line="20"
-            column="14"/>
-    </issue>
-
-</issues>
diff --git a/lint-baseline-go-res-lib.xml b/lint-baseline-go-res-lib.xml
deleted file mode 100644
index c5669f2..0000000
--- a/lint-baseline-go-res-lib.xml
+++ /dev/null
@@ -1,576 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.view.View#getWindowInsetsController`"
-        errorLine1="        getWindowInsetsController().hide(WindowInsets.Type.ime());"
-        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/allapps/AllAppsContainerView.java"
-            line="203"
-            column="9"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.view.WindowInsets.Type#ime`"
-        errorLine1="        getWindowInsetsController().hide(WindowInsets.Type.ime());"
-        errorLine2="                                                           ~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/allapps/AllAppsContainerView.java"
-            line="203"
-            column="60"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.view.WindowInsetsController#hide`"
-        errorLine1="        getWindowInsetsController().hide(WindowInsets.Type.ime());"
-        errorLine2="                                    ~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/allapps/AllAppsContainerView.java"
-            line="203"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.view.View#getWindowInsetsController`"
-        errorLine1="                getWindowInsetsController().hide(WindowInsets.Type.ime());"
-        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/allapps/AllAppsRecyclerView.java"
-            line="193"
-            column="17"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.view.WindowInsets.Type#ime`"
-        errorLine1="                getWindowInsetsController().hide(WindowInsets.Type.ime());"
-        errorLine2="                                                                   ~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/allapps/AllAppsRecyclerView.java"
-            line="193"
-            column="68"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.view.WindowInsetsController#hide`"
-        errorLine1="                getWindowInsetsController().hide(WindowInsets.Type.ime());"
-        errorLine2="                                            ~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/allapps/AllAppsRecyclerView.java"
-            line="193"
-            column="45"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 29): `android.appwidget.AppWidgetHostView#updateAppWidgetSize`"
-        errorLine1="            widgetView.updateAppWidgetSize(new Bundle(), sizes);"
-        errorLine2="                       ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/AppWidgetResizeFrame.java"
-            line="399"
-            column="24"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 29): `android.graphics.Rect#inset`"
-        errorLine1="        potentialTaskRect.inset(insets.left, insets.top, insets.right, insets.bottom);"
-        errorLine2="                          ~~~~~">
-        <location
-            file="packages/apps/Launcher3/quickstep/src/com/android/quickstep/BaseActivityInterface.java"
-            line="248"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 29): `android.graphics.Rect#inset`"
-        errorLine1="        potentialTaskRect.inset("
-        errorLine2="                          ~~~~~">
-        <location
-            file="packages/apps/Launcher3/quickstep/src/com/android/quickstep/BaseActivityInterface.java"
-            line="249"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 29): `android.graphics.Rect#inset`"
-        errorLine1="        outRect.inset(Math.max(insets.left, sideMargin), Math.max(insets.top, topMargin),"
-        errorLine2="                ~~~~~">
-        <location
-            file="packages/apps/Launcher3/quickstep/src/com/android/quickstep/BaseActivityInterface.java"
-            line="291"
-            column="17"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 29): `android.graphics.Rect#inset`"
-        errorLine1="        gridRect.inset(0, dp.overviewTaskThumbnailTopMarginPx, 0, 0);"
-        errorLine2="                 ~~~~~">
-        <location
-            file="packages/apps/Launcher3/quickstep/src/com/android/quickstep/BaseActivityInterface.java"
-            line="315"
-            column="18"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.view.WindowManager#getCurrentWindowMetrics`"
-        errorLine1="                    .getCurrentWindowMetrics().getWindowInsets();"
-        errorLine2="                     ~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/DeviceProfile.java"
-            line="236"
-            column="22"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.view.WindowMetrics#getWindowInsets`"
-        errorLine1="                    .getCurrentWindowMetrics().getWindowInsets();"
-        errorLine2="                                               ~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/DeviceProfile.java"
-            line="236"
-            column="48"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.content.Context#getDisplay`"
-        errorLine1="            if (mContext.getDisplay() != null) {"
-        errorLine2="                         ~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java"
-            line="115"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.content.Context#getDisplay`"
-        errorLine1="                mContext.getDisplay().getRealSize(mDisplaySize);"
-        errorLine2="                         ~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java"
-            line="116"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.content.ContextWrapper#getDisplay`"
-        errorLine1="        Display display = getDisplay();"
-        errorLine2="                          ~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java"
-            line="153"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `java.util.List#of`"
-        errorLine1="                    List.of(new Rect(0, 0, metrics.widthPixels, metrics.heightPixels)));"
-        errorLine2="                         ~~">
-        <location
-            file="packages/apps/Launcher3/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java"
-            line="158"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 29): `android.appwidget.AppWidgetHostView#resetColorResources`"
-        errorLine1="            resetColorResources();"
-        errorLine2="            ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java"
-            line="137"
-            column="13"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `java.util.List#of`"
-        errorLine1="            mColorExtractor.addLocation(List.of(mLastLocationRegistered));"
-        errorLine2="                                             ~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java"
-            line="367"
-            column="46"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `java.util.List#of`"
-        errorLine1="                mColorExtractor.addLocation(List.of(mLastLocationRegistered));"
-        errorLine2="                                                 ~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java"
-            line="390"
-            column="50"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 31 (current min is 29): `android.appwidget.AppWidgetProviderInfo#maxResizeWidth`"
-        errorLine1="                (ATLEAST_S &amp;&amp; maxResizeWidth > 0)"
-        errorLine2="                              ~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
-            line="91"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 31 (current min is 29): `android.appwidget.AppWidgetProviderInfo#maxResizeWidth`"
-        errorLine1="                        ? getSpanX(widgetPadding, maxResizeWidth, smallestCellWidth)"
-        errorLine2="                                                  ~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
-            line="92"
-            column="51"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 31 (current min is 29): `android.appwidget.AppWidgetProviderInfo#maxResizeHeight`"
-        errorLine1="                (ATLEAST_S &amp;&amp; maxResizeHeight > 0)"
-        errorLine2="                              ~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
-            line="95"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 31 (current min is 29): `android.appwidget.AppWidgetProviderInfo#maxResizeHeight`"
-        errorLine1="                        ? getSpanY(widgetPadding, maxResizeHeight, smallestCellHeight)"
-        errorLine2="                                                  ~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
-            line="96"
-            column="51"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 31 (current min is 29): `android.appwidget.AppWidgetProviderInfo#targetCellWidth`"
-        errorLine1="        if (ATLEAST_S &amp;&amp; targetCellWidth >= minSpanX &amp;&amp; targetCellWidth &lt;= maxSpanX"
-        errorLine2="                         ~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
-            line="101"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 31 (current min is 29): `android.appwidget.AppWidgetProviderInfo#targetCellWidth`"
-        errorLine1="        if (ATLEAST_S &amp;&amp; targetCellWidth >= minSpanX &amp;&amp; targetCellWidth &lt;= maxSpanX"
-        errorLine2="                                                        ~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
-            line="101"
-            column="57"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 31 (current min is 29): `android.appwidget.AppWidgetProviderInfo#targetCellHeight`"
-        errorLine1="                &amp;&amp; targetCellHeight >= minSpanY &amp;&amp; targetCellHeight &lt;= maxSpanY) {"
-        errorLine2="                   ~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
-            line="102"
-            column="20"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 31 (current min is 29): `android.appwidget.AppWidgetProviderInfo#targetCellHeight`"
-        errorLine1="                &amp;&amp; targetCellHeight >= minSpanY &amp;&amp; targetCellHeight &lt;= maxSpanY) {"
-        errorLine2="                                                   ~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
-            line="102"
-            column="52"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 31 (current min is 29): `android.appwidget.AppWidgetProviderInfo#targetCellWidth`"
-        errorLine1="            spanX = targetCellWidth;"
-        errorLine2="                    ~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
-            line="103"
-            column="21"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 31 (current min is 29): `android.appwidget.AppWidgetProviderInfo#targetCellHeight`"
-        errorLine1="            spanY = targetCellHeight;"
-        errorLine2="                    ~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
-            line="104"
-            column="21"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.content.Context#getDisplay`"
-        errorLine1="        final Display display = mContext.getDisplay();"
-        errorLine2="                                         ~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java"
-            line="94"
-            column="42"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 29): `android.content.pm.LauncherActivityInfo#getLoadingProgress`"
-        errorLine1="            return (int) (100 * info.getLoadingProgress());"
-        errorLine2="                                     ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/util/PackageManagerHelper.java"
-            line="338"
-            column="38"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `java.util.List#of`"
-        errorLine1="    private List&lt;WidgetsListBaseEntry> mAllWidgets = List.of();"
-        errorLine2="                                                          ~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/popup/PopupDataProvider.java"
-            line="64"
-            column="59"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `java.util.List#of`"
-        errorLine1="    private List&lt;ItemInfo> mRecommendedWidgets = List.of();"
-        errorLine2="                                                      ~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/popup/PopupDataProvider.java"
-            line="66"
-            column="55"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `new android.view.SurfaceControlViewHost`"
-        errorLine1="                    .submit(() -> new SurfaceControlViewHost(mContext, mDisplay, mHostToken))"
-        errorLine2="                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java"
-            line="91"
-            column="35"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.view.SurfaceControlViewHost#getSurfacePackage`"
-        errorLine1="            surfacePackage = mSurfaceControlViewHost.getSurfacePackage();"
-        errorLine2="                                                     ~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java"
-            line="93"
-            column="54"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.view.SurfaceControlViewHost#setView`"
-        errorLine1="                host.setView(view, view.getMeasuredWidth(), view.getMeasuredHeight());"
-        errorLine2="                     ~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java"
-            line="127"
-            column="22"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Cast from `SurfacePackage` to `Parcelable` requires API level 30 (current min is 29)"
-        errorLine1="        result.putParcelable(KEY_SURFACE_PACKAGE, surfacePackage);"
-        errorLine2="                                                  ~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java"
-            line="132"
-            column="51"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.view.SurfaceControlViewHost#release`"
-        errorLine1="                mSurfaceControlViewHost.release();"
-        errorLine2="                                        ~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java"
-            line="149"
-            column="41"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.graphics.Outline#setPath`"
-        errorLine1="        outline.setPath(mPath);"
-        errorLine2="                ~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/popup/RoundedArrowDrawable.java"
-            line="88"
-            column="17"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 29): `new android.widget.EdgeEffect`"
-        errorLine1="                ? new EdgeEffect(context, attrs) : new EdgeEffect(context);"
-        errorLine2="                  ~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/views/SpringRelativeLayout.java"
-            line="49"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 29): `new android.widget.EdgeEffect`"
-        errorLine1="                ? new EdgeEffect(context, attrs) : new EdgeEffect(context);"
-        errorLine2="                  ~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/views/SpringRelativeLayout.java"
-            line="51"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.view.WindowManager.LayoutParams#setFitInsetsTypes`"
-        errorLine1="        mWindowLayoutParams.setFitInsetsTypes(0);"
-        errorLine2="                            ~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java"
-            line="316"
-            column="29"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.view.WindowInsets#getInsets`"
-        errorLine1="            Insets systemInsets = insets.getInsets(WindowInsets.Type.systemBars());"
-        errorLine2="                                         ~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java"
-            line="118"
-            column="42"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.view.WindowInsets.Type#systemBars`"
-        errorLine1="            Insets systemInsets = insets.getInsets(WindowInsets.Type.systemBars());"
-        errorLine2="                                                                     ~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java"
-            line="118"
-            column="70"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 29): `android.appwidget.AppWidgetProviderInfo#loadDescription`"
-        errorLine1="            CharSequence description = mItem.widgetInfo.loadDescription(getContext());"
-        errorLine2="                                                        ~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/WidgetCell.java"
-            line="193"
-            column="57"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 31 (current min is 29): `android.appwidget.AppWidgetProviderInfo#previewLayout`"
-        errorLine1="                &amp;&amp; item.widgetInfo.previewLayout != Resources.ID_NULL) {"
-        errorLine2="                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/WidgetCell.java"
-            line="214"
-            column="20"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 31 (current min is 29): `android.appwidget.AppWidgetProviderInfo#previewLayout`"
-        errorLine1="            launcherAppWidgetProviderInfo.initialLayout = item.widgetInfo.previewLayout;"
-        errorLine2="                                                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/WidgetCell.java"
-            line="222"
-            column="59"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.view.View#getWindowInsetsController`"
-        errorLine1="        getWindowInsetsController().hide(WindowInsets.Type.ime());"
-        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java"
-            line="558"
-            column="9"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.view.WindowInsets.Type#ime`"
-        errorLine1="        getWindowInsetsController().hide(WindowInsets.Type.ime());"
-        errorLine2="                                                           ~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java"
-            line="558"
-            column="60"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.view.WindowInsetsController#hide`"
-        errorLine1="        getWindowInsetsController().hide(WindowInsets.Type.ime());"
-        errorLine2="                                    ~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java"
-            line="558"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `java.util.List#of`"
-        errorLine1="            return new RecommendationTableData(List.of(), previewScale);"
-        errorLine2="                                                    ~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java"
-            line="139"
-            column="53"/>
-    </issue>
-
-</issues>
diff --git a/lint-baseline-launcher3.xml b/lint-baseline-launcher3.xml
deleted file mode 100644
index 107a346..0000000
--- a/lint-baseline-launcher3.xml
+++ /dev/null
@@ -1,609 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 28 (current min is 26): `android.os.UserManager#requestQuietModeEnabled`"
-        errorLine1="            showConfirm |= !userManager.requestQuietModeEnabled(!toState, userProfile);"
-        errorLine2="                                        ~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/allapps/WorkModeSwitch.java"
-            line="110"
-            column="41"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 26): `android.view.View#getWindowInsetsController`"
-        errorLine1="        getWindowInsetsController().hide(WindowInsets.Type.ime());"
-        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/allapps/AllAppsContainerView.java"
-            line="203"
-            column="9"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 26): `android.view.WindowInsets.Type#ime`"
-        errorLine1="        getWindowInsetsController().hide(WindowInsets.Type.ime());"
-        errorLine2="                                                           ~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/allapps/AllAppsContainerView.java"
-            line="203"
-            column="60"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 26): `android.view.WindowInsetsController#hide`"
-        errorLine1="        getWindowInsetsController().hide(WindowInsets.Type.ime());"
-        errorLine2="                                    ~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/allapps/AllAppsContainerView.java"
-            line="203"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 26): `android.view.View#getWindowInsetsController`"
-        errorLine1="                getWindowInsetsController().hide(WindowInsets.Type.ime());"
-        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/allapps/AllAppsRecyclerView.java"
-            line="193"
-            column="17"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 26): `android.view.WindowInsets.Type#ime`"
-        errorLine1="                getWindowInsetsController().hide(WindowInsets.Type.ime());"
-        errorLine2="                                                                   ~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/allapps/AllAppsRecyclerView.java"
-            line="193"
-            column="68"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 26): `android.view.WindowInsetsController#hide`"
-        errorLine1="                getWindowInsetsController().hide(WindowInsets.Type.ime());"
-        errorLine2="                                            ~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/allapps/AllAppsRecyclerView.java"
-            line="193"
-            column="45"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 26): `android.appwidget.AppWidgetHostView#updateAppWidgetSize`"
-        errorLine1="            widgetView.updateAppWidgetSize(new Bundle(), sizes);"
-        errorLine2="                       ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/AppWidgetResizeFrame.java"
-            line="399"
-            column="24"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 26): `android.view.WindowManager#getCurrentWindowMetrics`"
-        errorLine1="                    .getCurrentWindowMetrics().getWindowInsets();"
-        errorLine2="                     ~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/DeviceProfile.java"
-            line="236"
-            column="22"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 26): `android.view.WindowMetrics#getWindowInsets`"
-        errorLine1="                    .getCurrentWindowMetrics().getWindowInsets();"
-        errorLine2="                                               ~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/DeviceProfile.java"
-            line="236"
-            column="48"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.content.res.Resources#getFloat`"
-        errorLine1="        folderLabelTextScale = res.getFloat(R.dimen.folder_label_text_scale);"
-        errorLine2="                                   ~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/DeviceProfile.java"
-            line="256"
-            column="36"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.content.res.Resources#getFloat`"
-        errorLine1="        return mContext.getResources().getFloat(resId);"
-        errorLine2="                                       ~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/util/DynamicResource.java"
-            line="73"
-            column="40"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 26): `android.appwidget.AppWidgetHostView#resetColorResources`"
-        errorLine1="            resetColorResources();"
-        errorLine2="            ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java"
-            line="137"
-            column="13"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 26): `java.util.List#of`"
-        errorLine1="            mColorExtractor.addLocation(List.of(mLastLocationRegistered));"
-        errorLine2="                                             ~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java"
-            line="367"
-            column="46"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 26): `java.util.List#of`"
-        errorLine1="                mColorExtractor.addLocation(List.of(mLastLocationRegistered));"
-        errorLine2="                                                 ~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java"
-            line="390"
-            column="50"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 31 (current min is 26): `android.appwidget.AppWidgetProviderInfo#maxResizeWidth`"
-        errorLine1="                (ATLEAST_S &amp;&amp; maxResizeWidth > 0)"
-        errorLine2="                              ~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
-            line="91"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 31 (current min is 26): `android.appwidget.AppWidgetProviderInfo#maxResizeWidth`"
-        errorLine1="                        ? getSpanX(widgetPadding, maxResizeWidth, smallestCellWidth)"
-        errorLine2="                                                  ~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
-            line="92"
-            column="51"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 31 (current min is 26): `android.appwidget.AppWidgetProviderInfo#maxResizeHeight`"
-        errorLine1="                (ATLEAST_S &amp;&amp; maxResizeHeight > 0)"
-        errorLine2="                              ~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
-            line="95"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 31 (current min is 26): `android.appwidget.AppWidgetProviderInfo#maxResizeHeight`"
-        errorLine1="                        ? getSpanY(widgetPadding, maxResizeHeight, smallestCellHeight)"
-        errorLine2="                                                  ~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
-            line="96"
-            column="51"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 31 (current min is 26): `android.appwidget.AppWidgetProviderInfo#targetCellWidth`"
-        errorLine1="        if (ATLEAST_S &amp;&amp; targetCellWidth >= minSpanX &amp;&amp; targetCellWidth &lt;= maxSpanX"
-        errorLine2="                         ~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
-            line="101"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 31 (current min is 26): `android.appwidget.AppWidgetProviderInfo#targetCellWidth`"
-        errorLine1="        if (ATLEAST_S &amp;&amp; targetCellWidth >= minSpanX &amp;&amp; targetCellWidth &lt;= maxSpanX"
-        errorLine2="                                                        ~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
-            line="101"
-            column="57"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 31 (current min is 26): `android.appwidget.AppWidgetProviderInfo#targetCellHeight`"
-        errorLine1="                &amp;&amp; targetCellHeight >= minSpanY &amp;&amp; targetCellHeight &lt;= maxSpanY) {"
-        errorLine2="                   ~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
-            line="102"
-            column="20"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 31 (current min is 26): `android.appwidget.AppWidgetProviderInfo#targetCellHeight`"
-        errorLine1="                &amp;&amp; targetCellHeight >= minSpanY &amp;&amp; targetCellHeight &lt;= maxSpanY) {"
-        errorLine2="                                                   ~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
-            line="102"
-            column="52"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 31 (current min is 26): `android.appwidget.AppWidgetProviderInfo#targetCellWidth`"
-        errorLine1="            spanX = targetCellWidth;"
-        errorLine2="                    ~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
-            line="103"
-            column="21"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 31 (current min is 26): `android.appwidget.AppWidgetProviderInfo#targetCellHeight`"
-        errorLine1="            spanY = targetCellHeight;"
-        errorLine2="                    ~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
-            line="104"
-            column="21"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 28 (current min is 26): `android.app.Person#getKey`"
-        errorLine1="        return people.stream().filter(person -> person.getKey() != null)"
-        errorLine2="                                                       ~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/notification/NotificationKeyData.java"
-            line="72"
-            column="56"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Method reference requires API level 28 (current min is 26): `Person::getKey`"
-        errorLine1="                .map(Person::getKey).sorted().toArray(String[]::new);"
-        errorLine2="                     ~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/notification/NotificationKeyData.java"
-            line="73"
-            column="22"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 26): `android.content.pm.LauncherActivityInfo#getLoadingProgress`"
-        errorLine1="            return (int) (100 * info.getLoadingProgress());"
-        errorLine2="                                     ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/util/PackageManagerHelper.java"
-            line="338"
-            column="38"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 29 (current min is 26): `android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_PAGE_LEFT`"
-        errorLine1="                AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_LEFT"
-        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/PagedView.java"
-            line="1752"
-            column="17"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 29 (current min is 26): `android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_PAGE_RIGHT`"
-        errorLine1="                : AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_RIGHT);"
-        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/PagedView.java"
-            line="1753"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 29 (current min is 26): `android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_PAGE_RIGHT`"
-        errorLine1="                AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_RIGHT"
-        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/PagedView.java"
-            line="1760"
-            column="17"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 29 (current min is 26): `android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_PAGE_LEFT`"
-        errorLine1="                : AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_LEFT);"
-        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/PagedView.java"
-            line="1761"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 26): `java.util.List#of`"
-        errorLine1="    private List&lt;WidgetsListBaseEntry> mAllWidgets = List.of();"
-        errorLine2="                                                          ~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/popup/PopupDataProvider.java"
-            line="64"
-            column="59"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 26): `java.util.List#of`"
-        errorLine1="    private List&lt;ItemInfo> mRecommendedWidgets = List.of();"
-        errorLine2="                                                      ~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/popup/PopupDataProvider.java"
-            line="66"
-            column="55"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 26): `new android.view.SurfaceControlViewHost`"
-        errorLine1="                    .submit(() -> new SurfaceControlViewHost(mContext, mDisplay, mHostToken))"
-        errorLine2="                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java"
-            line="91"
-            column="35"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 26): `android.view.SurfaceControlViewHost#getSurfacePackage`"
-        errorLine1="            surfacePackage = mSurfaceControlViewHost.getSurfacePackage();"
-        errorLine2="                                                     ~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java"
-            line="93"
-            column="54"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 26): `android.view.SurfaceControlViewHost#setView`"
-        errorLine1="                host.setView(view, view.getMeasuredWidth(), view.getMeasuredHeight());"
-        errorLine2="                     ~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java"
-            line="127"
-            column="22"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Cast from `SurfacePackage` to `Parcelable` requires API level 30 (current min is 26)"
-        errorLine1="        result.putParcelable(KEY_SURFACE_PACKAGE, surfacePackage);"
-        errorLine2="                                                  ~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java"
-            line="132"
-            column="51"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 26): `android.view.SurfaceControlViewHost#release`"
-        errorLine1="                mSurfaceControlViewHost.release();"
-        errorLine2="                                        ~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java"
-            line="149"
-            column="41"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 26): `android.graphics.Outline#setPath`"
-        errorLine1="        outline.setPath(mPath);"
-        errorLine2="                ~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/popup/RoundedArrowDrawable.java"
-            line="88"
-            column="17"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 26): `new android.widget.EdgeEffect`"
-        errorLine1="                ? new EdgeEffect(context, attrs) : new EdgeEffect(context);"
-        errorLine2="                  ~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/views/SpringRelativeLayout.java"
-            line="49"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 26): `new android.widget.EdgeEffect`"
-        errorLine1="                ? new EdgeEffect(context, attrs) : new EdgeEffect(context);"
-        errorLine2="                  ~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/views/SpringRelativeLayout.java"
-            line="51"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.WindowInsets#getTappableElementInsets`"
-        errorLine1="            return windowInsets.getTappableElementInsets().bottom > 0;"
-        errorLine2="                                ~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/graphics/SysUiScrim.java"
-            line="190"
-            column="33"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 29 (current min is 26): `android.graphics.Insets#bottom`"
-        errorLine1="            return windowInsets.getTappableElementInsets().bottom > 0;"
-        errorLine2="                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/graphics/SysUiScrim.java"
-            line="190"
-            column="20"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 28 (current min is 26): `android.appwidget.AppWidgetProviderInfo#widgetFeatures`"
-        errorLine1="        int featureFlags = mProviderInfo.widgetFeatures;"
-        errorLine2="                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/WidgetAddFlowHandler.java"
-            line="93"
-            column="28"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 26): `android.appwidget.AppWidgetProviderInfo#loadDescription`"
-        errorLine1="            CharSequence description = mItem.widgetInfo.loadDescription(getContext());"
-        errorLine2="                                                        ~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/WidgetCell.java"
-            line="193"
-            column="57"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 31 (current min is 26): `android.appwidget.AppWidgetProviderInfo#previewLayout`"
-        errorLine1="                &amp;&amp; item.widgetInfo.previewLayout != Resources.ID_NULL) {"
-        errorLine2="                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/WidgetCell.java"
-            line="214"
-            column="20"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 31 (current min is 26): `android.appwidget.AppWidgetProviderInfo#previewLayout`"
-        errorLine1="            launcherAppWidgetProviderInfo.initialLayout = item.widgetInfo.previewLayout;"
-        errorLine2="                                                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/WidgetCell.java"
-            line="222"
-            column="59"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 26): `android.view.View#getWindowInsetsController`"
-        errorLine1="        getWindowInsetsController().hide(WindowInsets.Type.ime());"
-        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java"
-            line="558"
-            column="9"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 26): `android.view.WindowInsets.Type#ime`"
-        errorLine1="        getWindowInsetsController().hide(WindowInsets.Type.ime());"
-        errorLine2="                                                           ~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java"
-            line="558"
-            column="60"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 26): `android.view.WindowInsetsController#hide`"
-        errorLine1="        getWindowInsetsController().hide(WindowInsets.Type.ime());"
-        errorLine2="                                    ~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java"
-            line="558"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 26): `java.util.List#of`"
-        errorLine1="            return new RecommendationTableData(List.of(), previewScale);"
-        errorLine2="                                                    ~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java"
-            line="139"
-            column="53"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Method reference requires API level 28 (current min is 26): `Person::getKey`"
-        errorLine1="            : Arrays.stream(persons).map(Person::getKey).sorted().toArray(String[]::new);"
-        errorLine2="                                         ~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/model/data/WorkspaceItemInfo.java"
-            line="178"
-            column="42"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 26): `android.appwidget.AppWidgetHostView#setColorResources`"
-        errorLine1="                setColorResources(mWallpaperColorResources);"
-        errorLine2="                ~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java"
-            line="528"
-            column="17"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 27 (current min is 26): `android.app.WallpaperManager#getWallpaperColors`"
-        errorLine1="                    : WallpaperManager.getInstance(context).getWallpaperColors(FLAG_SYSTEM);"
-        errorLine2="                                                            ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java"
-            line="288"
-            column="61"/>
-    </issue>
-
-</issues>
diff --git a/lint-baseline-res-lib.xml b/lint-baseline-res-lib.xml
deleted file mode 100644
index e52f8fb..0000000
--- a/lint-baseline-res-lib.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">
-
-    <issue
-        id="NewApi"
-        message="`?android:attr/dialogCornerRadius` requires API level 28 (current min is 26)"
-        errorLine1="        android:topLeftRadius=&quot;?android:attr/dialogCornerRadius&quot;"
-        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/res/drawable/add_item_dialog_background.xml"
-            line="6"
-            column="9"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="`?android:attr/dialogCornerRadius` requires API level 28 (current min is 26)"
-        errorLine1="        android:topRightRadius=&quot;?android:attr/dialogCornerRadius&quot; />"
-        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/res/drawable/add_item_dialog_background.xml"
-            line="7"
-            column="9"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="`@android:style/Widget.DeviceDefault.Button.Colored` requires API level 28 (current min is 26)"
-        errorLine1="    &lt;style name=&quot;Widget.DeviceDefault.Button.Rounded.Colored&quot; parent=&quot;@android:style/Widget.DeviceDefault.Button.Colored&quot;>"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/res/values/styles.xml"
-            line="287"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="`@android:dimen/system_app_widget_background_radius` requires API level 31 (current min is 26)"
-        errorLine1="    &lt;corners android:radius=&quot;@android:dimen/system_app_widget_background_radius&quot; />"
-        errorLine2="             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/apps/Launcher3/res/drawable/widget_resize_frame.xml"
-            line="20"
-            column="14"/>
-    </issue>
-
-</issues>
diff --git a/lint-baseline.xml b/lint-baseline.xml
index 23a22be..fe005ca 100644
--- a/lint-baseline.xml
+++ b/lint-baseline.xml
@@ -1,148 +1,169 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
 
     <issue
         id="NewApi"
-        message="Call requires API level 28 (current min is 26): `android.app.Person#getKey`">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/notification/NotificationKeyData.java"
-            line="72"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.content.res.Resources#getFloat`">
+        message="Call requires API level 29 (current min is 26): `android.content.res.Resources#getFloat`"
+        errorLine1="        return mContext.getResources().getFloat(resId);"
+        errorLine2="                                       ~~~~~~~~">
         <location
             file="packages/apps/Launcher3/src/com/android/launcher3/util/DynamicResource.java"
-            line="73"/>
+            line="73"
+            column="40"/>
     </issue>
 
     <issue
         id="NewApi"
-        message="Call requires API level 30 (current min is 26): `android.graphics.Outline#setPath`">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/popup/RoundedArrowDrawable.java"
-            line="114"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 30 (current min is 26): `android.view.View#getWindowInsetsController`">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java"
-            line="902"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 30 (current min is 26): `android.view.WindowInsets.Type#ime`">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java"
-            line="902"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 30 (current min is 26): `android.view.WindowInsetsController#hide`">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java"
-            line="902"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 26): `android.appwidget.AppWidgetHostView#resetColorResources`">
+        message="Call requires API level 31 (current min is 26): `android.appwidget.AppWidgetHostView#resetColorResources`"
+        errorLine1="            resetColorResources();"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~">
         <location
             file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java"
-            line="114"/>
+            line="117"
+            column="13"/>
     </issue>
 
     <issue
         id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.appwidget.AppWidgetHostView#setColorResources`">
+        message="Call requires API level 31 (current min is 30): `android.appwidget.AppWidgetHostView#setColorResources`"
+        errorLine1="            view.setColorResources(mWallpaperColorResources);"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~">
         <location
             file="packages/apps/Launcher3/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java"
-            line="415"/>
+            line="433"
+            column="18"/>
     </issue>
 
     <issue
         id="NewApi"
-        message="Field requires API level 28 (current min is 26): `android.appwidget.AppWidgetProviderInfo#widgetFeatures`">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/widget/WidgetAddFlowHandler.java"
-            line="93"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 29 (current min is 26): `android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_PAGE_LEFT`">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/PagedView.java"
-            line="1814"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 29 (current min is 26): `android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_PAGE_LEFT`">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/PagedView.java"
-            line="1824"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 29 (current min is 26): `android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_PAGE_RIGHT`">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/PagedView.java"
-            line="1815"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 29 (current min is 26): `android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_PAGE_RIGHT`">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/PagedView.java"
-            line="1823"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Method reference requires API level 28 (current min is 26): `Person::getKey`">
-        <location
-            file="packages/apps/Launcher3/src/com/android/launcher3/model/data/WorkspaceItemInfo.java"
-            line="195"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Method reference requires API level 28 (current min is 26): `Person::getKey`">
+        message="Call requires API level 28 (current min is 26): `android.app.Person#getKey`"
+        errorLine1="        return people.stream().filter(person -&gt; person.getKey() != null)"
+        errorLine2="                                                       ~~~~~~">
         <location
             file="packages/apps/Launcher3/src/com/android/launcher3/notification/NotificationKeyData.java"
-            line="73"/>
+            line="72"
+            column="56"/>
     </issue>
 
     <issue
         id="NewApi"
-        message="`?android:attr/dialogCornerRadius` requires API level 28 (current min is 26)">
+        message="Method reference requires API level 28 (current min is 26): `Person::getKey`"
+        errorLine1="                .map(Person::getKey).sorted().toArray(String[]::new);"
+        errorLine2="                     ~~~~~~~~~~~~~~">
         <location
-            file="packages/apps/Launcher3/res/drawable/add_item_dialog_background.xml"
-            line="6"/>
+            file="packages/apps/Launcher3/src/com/android/launcher3/notification/NotificationKeyData.java"
+            line="73"
+            column="22"/>
     </issue>
 
     <issue
         id="NewApi"
-        message="`?android:attr/dialogCornerRadius` requires API level 28 (current min is 26)">
+        message="Field requires API level 29 (current min is 26): `android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_PAGE_LEFT`"
+        errorLine1="                AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_LEFT"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
-            file="packages/apps/Launcher3/res/drawable/add_item_dialog_background.xml"
-            line="7"/>
+            file="packages/apps/Launcher3/src/com/android/launcher3/PagedView.java"
+            line="1814"
+            column="17"/>
     </issue>
 
     <issue
         id="NewApi"
-        message="`@android:dimen/system_app_widget_background_radius` requires API level 31 (current min is 26)">
+        message="Field requires API level 29 (current min is 26): `android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_PAGE_RIGHT`"
+        errorLine1="                : AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_RIGHT);"
+        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
-            file="packages/apps/Launcher3/res/drawable/widget_resize_frame.xml"
-            line="20"/>
+            file="packages/apps/Launcher3/src/com/android/launcher3/PagedView.java"
+            line="1815"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Field requires API level 29 (current min is 26): `android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_PAGE_RIGHT`"
+        errorLine1="                AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_RIGHT"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/Launcher3/src/com/android/launcher3/PagedView.java"
+            line="1823"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Field requires API level 29 (current min is 26): `android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_PAGE_LEFT`"
+        errorLine1="                : AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_LEFT);"
+        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/Launcher3/src/com/android/launcher3/PagedView.java"
+            line="1824"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 30 (current min is 26): `android.graphics.Outline#setPath`"
+        errorLine1="        outline.setPath(mPath);"
+        errorLine2="                ~~~~~~~">
+        <location
+            file="packages/apps/Launcher3/src/com/android/launcher3/popup/RoundedArrowDrawable.java"
+            line="114"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Field requires API level 28 (current min is 26): `android.appwidget.AppWidgetProviderInfo#widgetFeatures`"
+        errorLine1="        int featureFlags = mProviderInfo.widgetFeatures;"
+        errorLine2="                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/Launcher3/src/com/android/launcher3/widget/WidgetAddFlowHandler.java"
+            line="93"
+            column="28"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 30 (current min is 26): `android.view.View#getWindowInsetsController`"
+        errorLine1="        WindowInsetsController insetsController = getWindowInsetsController();"
+        errorLine2="                                                  ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/Launcher3/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java"
+            line="820"
+            column="51"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 30 (current min is 26): `android.view.WindowInsets.Type#ime`"
+        errorLine1="            insetsController.hide(WindowInsets.Type.ime());"
+        errorLine2="                                                    ~~~">
+        <location
+            file="packages/apps/Launcher3/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java"
+            line="822"
+            column="53"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 30 (current min is 26): `android.view.WindowInsetsController#hide`"
+        errorLine1="            insetsController.hide(WindowInsets.Type.ime());"
+        errorLine2="                             ~~~~">
+        <location
+            file="packages/apps/Launcher3/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java"
+            line="822"
+            column="30"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Method reference requires API level 28 (current min is 26): `Person::getKey`"
+        errorLine1="            : Arrays.stream(persons).map(Person::getKey).sorted().toArray(String[]::new);"
+        errorLine2="                                         ~~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/Launcher3/src/com/android/launcher3/model/data/WorkspaceItemInfo.java"
+            line="194"
+            column="42"/>
     </issue>
 
 </issues>
\ No newline at end of file
diff --git a/lint-baseline2.xml b/lint-baseline2.xml
new file mode 100644
index 0000000..84f1b15
--- /dev/null
+++ b/lint-baseline2.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
+
+    <issue
+        id="NewApi"
+        message="`?android:attr/dialogCornerRadius` requires API level 28 (current min is 26)"
+        errorLine1='        android:topLeftRadius="?android:attr/dialogCornerRadius"'
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/Launcher3/res/drawable/add_item_dialog_background.xml"
+            line="6"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="`?android:attr/dialogCornerRadius` requires API level 28 (current min is 26)"
+        errorLine1='        android:topRightRadius="?android:attr/dialogCornerRadius" /&gt;'
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/Launcher3/res/drawable/add_item_dialog_background.xml"
+            line="7"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="`@android:dimen/system_app_widget_background_radius` requires API level 31 (current min is 26)"
+        errorLine1='    &lt;corners android:radius="@android:dimen/system_app_widget_background_radius" /&gt;'
+        errorLine2="             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/Launcher3/res/drawable/widget_resize_frame.xml"
+            line="20"
+            column="14"/>
+    </issue>
+
+</issues>
\ No newline at end of file
diff --git a/quickstep/res/drawable-sw600dp-land/gesture_tutorial_back_step_shape.xml b/quickstep/res/drawable-sw600dp-land/gesture_tutorial_back_step_shape.xml
index a07aeaa..8b4127a 100644
--- a/quickstep/res/drawable-sw600dp-land/gesture_tutorial_back_step_shape.xml
+++ b/quickstep/res/drawable-sw600dp-land/gesture_tutorial_back_step_shape.xml
@@ -16,7 +16,8 @@
     android:width="84dp"
     android:height="208dp"
     android:viewportWidth="84"
-    android:viewportHeight="208">
+    android:viewportHeight="208"
+    android:autoMirrored="true">
   <path
       android:pathData="M24.53,169.2L32.09,165.56C77.7,143.55 77.7,64.45 32.09,42.35L24.53,38.71C14.55,33.95 6.06,25.56 0,14.92V193.08C6.06,182.44 14.55,174.05 24.53,169.2Z"
       android:fillColor="?attr/onSurfaceBack"/>
diff --git a/quickstep/res/drawable-sw720dp-land/gesture_tutorial_back_step_shape.xml b/quickstep/res/drawable-sw720dp-land/gesture_tutorial_back_step_shape.xml
index e20458e..3a11f21 100644
--- a/quickstep/res/drawable-sw720dp-land/gesture_tutorial_back_step_shape.xml
+++ b/quickstep/res/drawable-sw720dp-land/gesture_tutorial_back_step_shape.xml
@@ -16,7 +16,8 @@
     android:width="122dp"
     android:height="303dp"
     android:viewportWidth="122"
-    android:viewportHeight="303">
+    android:viewportHeight="303"
+    android:autoMirrored="true">
   <path
       android:pathData="M35.65,245.9L46.63,240.61C112.92,208.62 112.92,93.67 46.63,61.54L35.65,56.26C21.15,49.34 8.81,37.14 0,21.69V280.6C8.81,265.15 21.15,252.95 35.65,245.9Z"
       android:fillColor="?attr/onSurfaceBack"/>
diff --git a/quickstep/res/drawable/gesture_tutorial_back_step_shape.xml b/quickstep/res/drawable/gesture_tutorial_back_step_shape.xml
index 9389340..c217be2 100644
--- a/quickstep/res/drawable/gesture_tutorial_back_step_shape.xml
+++ b/quickstep/res/drawable/gesture_tutorial_back_step_shape.xml
@@ -16,7 +16,8 @@
     android:width="83dp"
     android:height="208dp"
     android:viewportWidth="83"
-    android:viewportHeight="208">
+    android:viewportHeight="208"
+    android:autoMirrored="true">
   <path
       android:pathData="M23.53,169.2L31.09,165.56C76.7,143.55 76.7,64.45 31.09,42.35L23.53,38.71C13.55,33.95 5.06,25.56 -1,14.92V193.08C5.06,182.44 13.55,174.05 23.53,169.2Z"
       android:fillColor="?attr/onSurfaceBack"/>
diff --git a/quickstep/res/values-zh-rCN/strings.xml b/quickstep/res/values-zh-rCN/strings.xml
index 2c740b7..4d21108 100644
--- a/quickstep/res/values-zh-rCN/strings.xml
+++ b/quickstep/res/values-zh-rCN/strings.xml
@@ -114,7 +114,7 @@
     <string name="taskbar_edu_close" msgid="887022990168191073">"关闭"</string>
     <string name="taskbar_edu_done" msgid="6880178093977704569">"完成"</string>
     <string name="taskbar_button_home" msgid="2151398979630664652">"主屏幕"</string>
-    <string name="taskbar_button_a11y" msgid="5241161324875094465">"无障碍"</string>
+    <string name="taskbar_button_a11y" msgid="5241161324875094465">"无障碍功能"</string>
     <string name="taskbar_button_back" msgid="8558862226461164514">"返回"</string>
     <string name="taskbar_button_ime_switcher" msgid="1730244360907588541">"IME 切换器"</string>
     <string name="taskbar_button_recents" msgid="7273376136216613134">"最近用过"</string>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 232c441..2a1f39f 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -322,7 +322,6 @@
 
     <!-- Taskbar -->
     <dimen name="taskbar_size">@*android:dimen/taskbar_frame_height</dimen>
-    <dimen name="taskbar_phone_size">@*android:dimen/navigation_bar_frame_height</dimen>
     <dimen name="taskbar_ime_size">48dp</dimen>
     <dimen name="taskbar_icon_min_touch_size">48dp</dimen>
     <!-- Note that this applies to both sides of all icons, so visible space is double this. -->
diff --git a/quickstep/src/com/android/launcher3/HomeTransitionController.java b/quickstep/src/com/android/launcher3/HomeTransitionController.java
index b264115..2b50283 100644
--- a/quickstep/src/com/android/launcher3/HomeTransitionController.java
+++ b/quickstep/src/com/android/launcher3/HomeTransitionController.java
@@ -28,19 +28,16 @@
  */
 public class HomeTransitionController {
 
-    private final QuickstepLauncher mLauncher;
+    @Nullable private QuickstepLauncher mLauncher;
     @Nullable private IHomeTransitionListener mHomeTransitionListener;
 
-    public HomeTransitionController(QuickstepLauncher launcher) {
+    public void registerHomeTransitionListener(QuickstepLauncher launcher) {
         mLauncher = launcher;
-    }
-
-    public void registerHomeTransitionListener() {
         mHomeTransitionListener = new IHomeTransitionListener.Stub() {
             @Override
             public void onHomeVisibilityChanged(boolean isVisible) {
                 MAIN_EXECUTOR.execute(() -> {
-                    if (mLauncher.getTaskbarUIController() != null) {
+                    if (mLauncher != null && mLauncher.getTaskbarUIController() != null) {
                         mLauncher.getTaskbarUIController().onLauncherVisibilityChanged(isVisible);
                     }
                 });
@@ -53,5 +50,6 @@
     public void unregisterHomeTransitionListener() {
         SystemUiProxy.INSTANCE.get(mLauncher).setHomeTransitionListener(null);
         mHomeTransitionListener = null;
+        mLauncher = null;
     }
 }
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index f25b652..5890f14 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -1161,6 +1161,7 @@
         SystemUiProxy.INSTANCE.get(mLauncher)
                 .registerRemoteTransition(mLauncherOpenTransition, homeCheck);
         if (mBackAnimationController != null) {
+            mBackAnimationController.registerComponentCallbacks();
             mBackAnimationController.registerBackCallbacks(mHandler);
         }
     }
@@ -1168,6 +1169,7 @@
     public void onActivityDestroyed() {
         unregisterRemoteAnimations();
         unregisterRemoteTransitions();
+        mLauncher.removeOnDeviceProfileChangeListener(this);
         SystemUiProxy.INSTANCE.get(mLauncher).setStartingWindowListener(null);
         ORDERED_BG_EXECUTOR.execute(() -> mLauncher.getContentResolver()
                 .unregisterContentObserver(mAnimationRemovalObserver));
@@ -1200,6 +1202,7 @@
         mWallpaperOpenTransitionRunner = null;
         if (mBackAnimationController != null) {
             mBackAnimationController.unregisterBackCallbacks();
+            mBackAnimationController.unregisterComponentCallbacks();
             mBackAnimationController = null;
         }
     }
@@ -1290,7 +1293,7 @@
     /**
      * Returns view on launcher that corresponds to the closing app in the list of app targets
      */
-    private @Nullable View findLauncherView(RemoteAnimationTarget[] appTargets) {
+    public @Nullable View findLauncherView(RemoteAnimationTarget[] appTargets) {
         for (RemoteAnimationTarget appTarget : appTargets) {
             if (appTarget.mode == MODE_CLOSING) {
                 View launcherView = findLauncherView(appTarget);
@@ -1607,7 +1610,7 @@
                 || mLauncher.getWorkspace().isOverlayShown()
                 || shouldPlayFallbackClosingAnimation(appTargets);
 
-        boolean playWorkspaceReveal = true;
+        boolean playWorkspaceReveal = !fromPredictiveBack;
         boolean skipAllAppsScale = false;
         if (fromUnlock) {
             anim.play(getUnlockWindowAnimator(appTargets, wallpaperTargets));
@@ -1649,7 +1652,7 @@
         // targets list because it is already visible). In that case, we force
         // invisibility on touch down, and only reset it after the animation to home
         // is initialized.
-        if (launcherIsForceInvisibleOrOpening) {
+        if (launcherIsForceInvisibleOrOpening || fromPredictiveBack) {
             addCujInstrumentation(anim, playFallBackAnimation
                     ? Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK
                     : Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME);
@@ -1666,7 +1669,7 @@
             // Only register the content animation for cancellation when state changes
             mLauncher.getStateManager().setCurrentAnimation(anim);
 
-            if (mLauncher.isInState(LauncherState.ALL_APPS)) {
+            if (mLauncher.isInState(LauncherState.ALL_APPS) && !fromPredictiveBack) {
                 Pair<AnimatorSet, Runnable> contentAnimator =
                         getLauncherContentAnimator(false, LAUNCHER_RESUME_START_DELAY,
                                 skipAllAppsScale);
@@ -1677,10 +1680,8 @@
                         contentAnimator.second.run();
                     }
                 });
-            } else {
-                if (playWorkspaceReveal) {
+            } else if (playWorkspaceReveal) {
                     anim.play(new WorkspaceRevealAnim(mLauncher, false).getAnimators());
-                }
             }
         }
 
diff --git a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
index 477cd84..575ad9e 100644
--- a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
+++ b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3;
 
+import static android.content.ClipDescription.MIMETYPE_TEXT_INTENT;
 import static android.view.WindowInsets.Type.navigationBars;
 import static android.view.WindowInsets.Type.statusBars;
 
@@ -23,6 +24,8 @@
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
 import android.appwidget.AppWidgetProviderInfo;
+import android.content.ClipData;
+import android.content.ClipDescription;
 import android.content.Intent;
 import android.os.Bundle;
 import android.view.View;
@@ -43,6 +46,14 @@
 
 /** An Activity that can host Launcher's widget picker. */
 public class WidgetPickerActivity extends BaseActivity {
+    /**
+     * Name of the extra that indicates that a widget being dragged.
+     *
+     * <p>When set to "true" in the result of startActivityForResult, the client that launched the
+     * picker knows that activity was closed due to pending drag.
+     */
+    private static final String EXTRA_IS_PENDING_WIDGET_DRAG = "is_pending_widget_drag";
+
     private SimpleDragLayer<WidgetPickerActivity> mDragLayer;
     private WidgetsModel mModel;
     private final PopupDataProvider mPopupDataProvider = new PopupDataProvider(i -> {});
@@ -102,6 +113,46 @@
         };
     }
 
+    @Override
+    public View.OnLongClickListener getAllAppsItemLongClickListener() {
+        return view -> {
+            if (!(view instanceof WidgetCell widgetCell)) return false;
+
+            if (widgetCell.getWidgetView().getDrawable() == null
+                    && widgetCell.getAppWidgetHostViewPreview() == null) {
+                // The widget preview hasn't been loaded; so, we abort the drag.
+                return false;
+            }
+
+            final AppWidgetProviderInfo info = widgetCell.getWidgetItem().widgetInfo;
+            if (info == null || info.provider == null) {
+                return false;
+            }
+
+            ClipData clipData = new ClipData(
+                    new ClipDescription(
+                            /* label= */ "", // not displayed anywhere; so, set to empty.
+                            new String[]{MIMETYPE_TEXT_INTENT}
+                    ),
+                    new ClipData.Item(new Intent()
+                            .putExtra(Intent.EXTRA_USER, info.getProfile())
+                            .putExtra(Intent.EXTRA_COMPONENT_NAME, info.provider))
+            );
+
+            // Set result indicating activity was closed due a widget being dragged.
+            setResult(RESULT_OK, new Intent()
+                    .putExtra(EXTRA_IS_PENDING_WIDGET_DRAG, true));
+
+            // DRAG_FLAG_GLOBAL permits dragging data beyond app window.
+            return view.startDragAndDrop(
+                    clipData,
+                    new View.DragShadowBuilder(view),
+                    /* myLocalState= */ null,
+                    View.DRAG_FLAG_GLOBAL
+            );
+        };
+    }
+
     private void refreshAndBindWidgets() {
         MODEL_EXECUTOR.execute(() -> {
             LauncherAppState app = LauncherAppState.getInstance(this);
diff --git a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
index 1440498..f012197 100644
--- a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -23,6 +23,7 @@
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.LinearLayout;
 
 import androidx.annotation.NonNull;
@@ -31,6 +32,7 @@
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
+import com.android.launcher3.Flags;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.FloatingHeaderRow;
@@ -54,6 +56,11 @@
 
     private final T mActivityContext;
     private int mNumPredictedAppsPerRow;
+    // Vertical padding of the icon that contributes to the expected cell height.
+    private final int mVerticalPadding;
+    // Extra padding that is used in the top app rows (prediction and search) that is not used in
+    // the regular A-Z list. This only applies to single line label.
+    private final int mTopRowExtraHeight;
 
     // Helper to drawing the focus indicator.
     private final FocusIndicatorHelper mFocusHelper;
@@ -76,6 +83,10 @@
         mFocusHelper = new SimpleFocusIndicatorHelper(this);
         mActivityContext = ActivityContext.lookupContext(context);
         mNumPredictedAppsPerRow = mActivityContext.getDeviceProfile().numShownAllAppsColumns;
+        mTopRowExtraHeight = getResources().getDimensionPixelSize(
+                R.dimen.all_apps_search_top_row_extra_height);
+        mVerticalPadding = getResources().getDimensionPixelSize(
+                R.dimen.all_apps_predicted_icon_vertical_padding);
         updateVisibility();
     }
 
@@ -124,13 +135,11 @@
         int iconHeight = deviceProfile.allAppsIconSizePx;
         int iconPadding = deviceProfile.allAppsIconDrawablePaddingPx;
         int textHeight = Utilities.calculateTextHeight(deviceProfile.allAppsIconTextSizePx);
-        int verticalPadding = getResources().getDimensionPixelSize(
-                R.dimen.all_apps_predicted_icon_vertical_padding);
-        int totalHeight = iconHeight + iconPadding + textHeight + verticalPadding * 2;
-        if (FeatureFlags.enableTwolineAllapps()) {
-            // Add extra textHeight to the existing total height.
-            totalHeight += textHeight;
-        }
+        int totalHeight = iconHeight + iconPadding + textHeight + mVerticalPadding * 2;
+        // Prediction row height will be 4dp bigger than the regular apps in A-Z list when two line
+        // is not enabled. Otherwise, the extra height will increase by just the textHeight.
+        int extraHeight = FeatureFlags.enableTwolineAllapps() ? textHeight : mTopRowExtraHeight;
+        totalHeight += extraHeight;
         return getVisibility() == GONE ? 0 : totalHeight + getPaddingTop() + getPaddingBottom();
     }
 
@@ -199,8 +208,12 @@
                 icon.setOnFocusChangeListener(mFocusHelper);
 
                 LayoutParams lp = (LayoutParams) icon.getLayoutParams();
-                // Ensure the all apps icon height matches the workspace icons in portrait mode.
-                lp.height = mActivityContext.getDeviceProfile().allAppsCellHeightPx;
+                if (Flags.enableFocusOutline()) {
+                    lp.height = ViewGroup.LayoutParams.MATCH_PARENT;
+                } else {
+                    // Ensure the all apps icon height matches the workspace icons in portrait mode.
+                    lp.height = mActivityContext.getDeviceProfile().allAppsCellHeightPx;
+                }
                 lp.width = 0;
                 lp.weight = 1;
                 addView(icon);
diff --git a/quickstep/src/com/android/launcher3/model/AppEventProducer.java b/quickstep/src/com/android/launcher3/model/AppEventProducer.java
index 0dde1bd..7209bd8 100644
--- a/quickstep/src/com/android/launcher3/model/AppEventProducer.java
+++ b/quickstep/src/com/android/launcher3/model/AppEventProducer.java
@@ -25,6 +25,7 @@
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
 import static com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers.ContainerCase.DEVICE_SEARCH_RESULT_CONTAINER;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_DRAGDROP;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_DISMISS_PREDICTION_UNDO;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_CONVERTED_TO_ICON;
@@ -71,12 +72,13 @@
 import com.android.launcher3.logging.StatsLogManager.EventEnum;
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.shortcuts.ShortcutRequest;
+import com.android.launcher3.util.UserIconInfo;
 import com.android.quickstep.logging.StatsLogCompatManager.StatsLogConsumer;
+import com.android.systemui.shared.system.SysUiStatsLog;
 
 import java.util.Locale;
 import java.util.Optional;
 import java.util.function.ObjIntConsumer;
-import java.util.function.Predicate;
 
 /**
  * Utility class to track stats log and emit corresponding app events
@@ -132,7 +134,8 @@
                 || event == LAUNCHER_TASK_LAUNCH_SWIPE_DOWN
                 || event == LAUNCHER_TASK_LAUNCH_TAP
                 || event == LAUNCHER_QUICKSWITCH_RIGHT
-                || event == LAUNCHER_QUICKSWITCH_LEFT) {
+                || event == LAUNCHER_QUICKSWITCH_LEFT
+                || event == LAUNCHER_APP_LAUNCH_DRAGDROP) {
             sendEvent(atomInfo, ACTION_LAUNCH, CONTAINER_PREDICTION);
         } else if (event == LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST) {
             sendEvent(atomInfo, ACTION_DISMISS, CONTAINER_PREDICTION);
@@ -186,14 +189,13 @@
     }
 
     @Nullable
-    private AppTarget toAppTarget(LauncherAtom.ItemInfo info) {
-        UserHandle userHandle = Process.myUserHandle();
-        if (info.getIsWork()) {
-            userHandle = UserCache.INSTANCE.get(mContext).getUserProfiles().stream()
-                    .filter(((Predicate<UserHandle>) userHandle::equals).negate())
-                    .findAny()
-                    .orElse(null);
-        }
+    AppTarget toAppTarget(LauncherAtom.ItemInfo info) {
+        int iconInfoType = getIconInfoTypeFromItemInfo(info);
+        UserCache userCache = UserCache.getInstance(mContext);
+        UserHandle userHandle = userCache.getUserProfiles().stream()
+                .filter(user -> userCache.getUserInfo(user).type == iconInfoType)
+                .findFirst()
+                .orElse(null);
         if (userHandle == null) {
             return null;
         }
@@ -287,6 +289,9 @@
             case SHORTCUTS_CONTAINER: {
                 return "deep-shortcuts";
             }
+            case TASK_BAR_CONTAINER: {
+                return "taskbar";
+            }
             case FOLDER: {
                 FolderContainer fc = ci.getFolder();
                 switch (fc.getParentContainerCase()) {
@@ -323,4 +328,16 @@
         return TextUtils.isEmpty(componentNameString)
                 ? null : ComponentName.unflattenFromString(componentNameString);
     }
+
+    private int getIconInfoTypeFromItemInfo(LauncherAtom.ItemInfo info) {
+        int userType = info.getUserType();
+        return switch (userType) {
+            case SysUiStatsLog.LAUNCHER_UICHANGED__USER_TYPE__TYPE_WORK -> UserIconInfo.TYPE_WORK;
+            case SysUiStatsLog.LAUNCHER_UICHANGED__USER_TYPE__TYPE_CLONED ->
+                    UserIconInfo.TYPE_CLONED;
+            case SysUiStatsLog.LAUNCHER_UICHANGED__USER_TYPE__TYPE_PRIVATE ->
+                    UserIconInfo.TYPE_PRIVATE;
+            default -> UserIconInfo.TYPE_MAIN;
+        };
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
index a7e8118..b7e1092 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.statehandlers;
 
+import static android.view.View.VISIBLE;
+
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
@@ -269,6 +271,9 @@
         if (dragLayer != null) {
             dragLayer.setVisibility(visibility);
         }
+        if (mLauncher instanceof QuickstepLauncher ql && ql.getTaskbarUIController() != null) {
+            ql.getTaskbarUIController().onLauncherVisibilityChanged(visibility == VISIBLE);
+        }
     }
 
     private void markLauncherPaused() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
index f981610..535c8ec 100644
--- a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
@@ -105,6 +105,13 @@
     }
 
     @Override
+    protected boolean isInOverview() {
+        TopTaskTracker.CachedTaskInfo topTask = TopTaskTracker.INSTANCE
+                .get(mControllers.taskbarActivityContext).getCachedTopTask(true);
+        return topTask.isRecentsTask();
+    }
+
+    @Override
     public RecentsView getRecentsView() {
         return mRecentsActivity.getOverviewPanel();
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
index f58fd45..5caf004 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
@@ -32,6 +32,7 @@
 import com.android.quickstep.util.GroupTask;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -115,8 +116,15 @@
                         && desktopController.areFreeformTasksVisible();
 
         if (mModel.isTaskListValid(mTaskListChangeId)) {
-            mQuickSwitchViewController.openQuickSwitchView(mTasks,
-                    mNumHiddenTasks, /* updateTasks= */ false, currentFocusedIndex, onDesktop);
+            // When we are opening the KQS with no focus override, check if the first task is
+            // running. If not, focus that first task.
+            mQuickSwitchViewController.openQuickSwitchView(
+                    mTasks,
+                    mNumHiddenTasks,
+                    /* updateTasks= */ false,
+                    currentFocusedIndex == -1 && !mControllerCallbacks.isFirstTaskRunning()
+                            ? 0 : currentFocusedIndex,
+                    onDesktop);
             return;
         }
 
@@ -126,8 +134,15 @@
             } else {
                 processLoadedTasks(tasks);
             }
-            mQuickSwitchViewController.openQuickSwitchView(mTasks,
-                    mNumHiddenTasks, /* updateTasks= */ true, currentFocusedIndex, onDesktop);
+            // Check if the first task is running after the recents model has updated so that we use
+            // the correct index.
+            mQuickSwitchViewController.openQuickSwitchView(
+                    mTasks,
+                    mNumHiddenTasks,
+                    /* updateTasks= */ true,
+                    currentFocusedIndex == -1 && !mControllerCallbacks.isFirstTaskRunning()
+                            ? 0 : currentFocusedIndex,
+                    onDesktop);
         });
     }
 
@@ -246,5 +261,20 @@
         void onCloseComplete() {
             mQuickSwitchViewController = null;
         }
+
+        boolean isTaskRunning(@Nullable GroupTask task) {
+            if (task == null) {
+                return false;
+            }
+            int runningTaskId = ActivityManagerWrapper.getInstance().getRunningTask().taskId;
+            Task task2 = task.task2;
+
+            return runningTaskId == task.task1.key.id
+                    || (task2 != null && runningTaskId == task2.key.id);
+        }
+
+        boolean isFirstTaskRunning() {
+            return isTaskRunning(getTaskAt(0));
+        }
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
index b29ce6b..9dac89d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
@@ -15,20 +15,26 @@
  */
 package com.android.launcher3.taskbar;
 
+import static android.window.SplashScreen.SPLASH_SCREEN_STYLE_UNDEFINED;
+
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 
 import android.animation.Animator;
+import android.app.ActivityOptions;
 import android.view.KeyEvent;
 import android.view.View;
+import android.window.RemoteTransition;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorListeners;
 import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
 import com.android.launcher3.taskbar.overlay.TaskbarOverlayDragLayer;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SlideInRemoteTransition;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -117,9 +123,12 @@
      * index will be focused.
      */
     protected int launchFocusedTask() {
-        // Launch the second-most recent task if the user quick switches too quickly, if possible.
-        return launchTaskAt(mCurrentFocusIndex == -1
-                ? (mControllerCallbacks.getTaskCount() > 1 ? 1 : 0) : mCurrentFocusIndex);
+        if (mCurrentFocusIndex != -1) {
+            return launchTaskAt(mCurrentFocusIndex);
+        }
+        // If the user quick switches too quickly, updateCurrentFocusIndex might not have run.
+        return launchTaskAt(mControllerCallbacks.isFirstTaskRunning()
+                && mControllerCallbacks.getTaskCount() > 1 ? 1 : 0);
     }
 
     private int launchTaskAt(int index) {
@@ -134,27 +143,28 @@
         if (task == null) {
             return Math.max(0, index);
         }
-        Task task2 = task.task2;
-        int runningTaskId = ActivityManagerWrapper.getInstance().getRunningTask().taskId;
-        if (runningTaskId == task.task1.key.id
-                || (task2 != null && runningTaskId == task2.key.id)) {
+        if (mControllerCallbacks.isTaskRunning(task)) {
             // Ignore attempts to run the selected task if it is already running.
             return -1;
         }
 
+        RemoteTransition remoteTransition = new RemoteTransition(new SlideInRemoteTransition(
+                Utilities.isRtl(mControllers.taskbarActivityContext.getResources())));
         if (mOnDesktop) {
             UI_HELPER_EXECUTOR.execute(() ->
                     SystemUiProxy.INSTANCE.get(mKeyboardQuickSwitchView.getContext())
                             .showDesktopApp(task.task1.key.id));
-        } else if (task2 == null) {
-            UI_HELPER_EXECUTOR.execute(() ->
-                    ActivityManagerWrapper.getInstance().startActivityFromRecents(
-                            task.task1.key,
-                            mControllers.taskbarActivityContext.getActivityLaunchOptions(
-                                    taskView == null ? mKeyboardQuickSwitchView : taskView, null)
-                                    .options));
+        } else if (task.task2 == null) {
+            UI_HELPER_EXECUTOR.execute(() -> {
+                ActivityOptions activityOptions = mControllers.taskbarActivityContext
+                        .makeDefaultActivityOptions(SPLASH_SCREEN_STYLE_UNDEFINED).options;
+                activityOptions.setRemoteTransition(remoteTransition);
+
+                ActivityManagerWrapper.getInstance().startActivityFromRecents(
+                        task.task1.key, activityOptions);
+            });
         } else {
-            mControllers.uiController.launchSplitTasks(task);
+            mControllers.uiController.launchSplitTasks(task, remoteTransition);
         }
         return -1;
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 159a6ef..20e977b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -20,6 +20,7 @@
 import static com.android.launcher3.taskbar.TaskbarEduTooltipControllerKt.TOOLTIP_STEP_FEATURES;
 import static com.android.launcher3.taskbar.TaskbarLauncherStateController.FLAG_VISIBLE;
 import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
+import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
@@ -27,6 +28,7 @@
 import android.util.Log;
 import android.view.TaskTransitionSpec;
 import android.view.WindowManagerGlobal;
+import android.window.RemoteTransition;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -40,11 +42,13 @@
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.InstanceIdSequence;
 import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.statehandlers.DesktopVisibilityController;
 import com.android.launcher3.taskbar.bubbles.BubbleBarController;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.MultiPropertyFactory;
 import com.android.launcher3.util.OnboardingPrefs;
+import com.android.quickstep.LauncherActivityInterface;
 import com.android.quickstep.RecentsAnimationCallbacks;
 import com.android.quickstep.util.GroupTask;
 import com.android.quickstep.views.RecentsView;
@@ -199,6 +203,16 @@
             return null;
         }
 
+        DesktopVisibilityController desktopController =
+                LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
+        final boolean onDesktop =
+                isDesktopModeSupported()
+                        && desktopController != null
+                        && desktopController.areFreeformTasksVisible();
+        if (onDesktop) {
+            isVisible = false;
+        }
+
         mTaskbarLauncherStateController.updateStateForFlag(FLAG_VISIBLE, isVisible);
         return mTaskbarLauncherStateController.applyState(fromInit ? 0 : duration, startAnimation);
     }
@@ -390,8 +404,9 @@
     }
 
     @Override
-    public void launchSplitTasks(@NonNull GroupTask groupTask) {
-        mLauncher.launchSplitTasks(groupTask);
+    public void launchSplitTasks(
+            @NonNull GroupTask groupTask, @Nullable RemoteTransition remoteTransition) {
+        mLauncher.launchSplitTasks(groupTask, remoteTransition);
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 709d3ba..6d4fc18 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -61,6 +61,7 @@
 import android.graphics.Region;
 import android.graphics.Region.Op;
 import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.Drawable;
 import android.graphics.drawable.PaintDrawable;
 import android.graphics.drawable.RotateDrawable;
 import android.inputmethodservice.InputMethodService;
@@ -96,6 +97,7 @@
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.util.window.WindowManagerProxy;
 import com.android.launcher3.views.BaseDragLayer;
+import com.android.systemui.shared.navigationbar.KeyButtonRipple;
 import com.android.systemui.shared.rotation.FloatingRotationButton;
 import com.android.systemui.shared.rotation.RotationButton;
 import com.android.systemui.shared.rotation.RotationButtonController;
@@ -665,6 +667,11 @@
 
         for (ImageView button : mAllButtons) {
             button.setImageTintList(ColorStateList.valueOf(iconColor));
+            Drawable background = button.getBackground();
+            if (background instanceof KeyButtonRipple) {
+                ((KeyButtonRipple) background).setDarkIntensity(
+                        mTaskbarNavButtonDarkIntensity.value);
+            }
         }
     }
 
@@ -751,6 +758,7 @@
                             mA11yButton, res, isInKidsMode, isInSetup, isThreeButtonNav,
                             mContext.isPhoneMode(), mWindowManagerProxy.getRotation(mContext));
             navButtonLayoutter.layoutButtons(mContext, isA11yButtonPersistent());
+            updateButtonsBackground();
             updateNavButtonColor();
             return;
         }
@@ -870,7 +878,27 @@
                 }
             }
         }
+    }
 
+    private void updateButtonsBackground() {
+        boolean clipped = !mContext.isPhoneButtonNavMode();
+        mNavButtonContainer.setClipToPadding(clipped);
+        mNavButtonContainer.setClipChildren(clipped);
+        mNavButtonsView.setClipToPadding(clipped);
+        mNavButtonsView.setClipChildren(clipped);
+
+        for (ImageView button : mAllButtons) {
+            updateButtonBackground(button, mContext.isPhoneButtonNavMode());
+        }
+    }
+
+    private static void updateButtonBackground(View view, boolean isPhoneButtonNavMode) {
+        if (isPhoneButtonNavMode) {
+            view.setBackground(new KeyButtonRipple(view.getContext(), view,
+                    R.dimen.key_button_ripple_max_width));
+        } else {
+            view.setBackgroundResource(R.drawable.taskbar_icon_click_feedback_roundrect);
+        }
     }
 
     public void onDestroy() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
index ad2dc23..382276a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.taskbar;
 
+import static android.view.Display.DEFAULT_DISPLAY;
+
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
 
 import android.animation.Animator;
@@ -83,6 +85,7 @@
 
     // States that affect whether region sampling is enabled or not
     private boolean mIsStashed;
+    private boolean mIsLumaSamplingEnabled;
     private boolean mTaskbarHidden;
 
     private float mTranslationYForSwipe;
@@ -149,7 +152,7 @@
         });
         initRegionSampler();
         if (mActivity.isPhoneGestureNavMode()) {
-            onIsStashedChanged(true);
+            onIsStashedChanged();
         }
     }
 
@@ -232,10 +235,23 @@
     }
 
     /** Called when taskbar is stashed or unstashed. */
-    public void onIsStashedChanged(boolean isStashed) {
-        mIsStashed = isStashed;
+    public void onIsStashedChanged() {
+        mIsStashed = isStashedHandleVisible();
+        updateSamplingState();
+    }
+
+    public void onNavigationBarLumaSamplingEnabled(int displayId, boolean enable) {
+        if (DEFAULT_DISPLAY != displayId) {
+            return;
+        }
+
+        mIsLumaSamplingEnabled = enable;
+        updateSamplingState();
+    }
+
+    private void updateSamplingState() {
         updateRegionSamplingWindowVisibility();
-        if (isStashed) {
+        if (shouldSample()) {
             mStashedHandleView.updateSampledRegion(mStashedHandleBounds);
             mRegionSamplingHelper.start(mStashedHandleView.getSampledRegion());
         } else {
@@ -243,6 +259,10 @@
         }
     }
 
+    private boolean shouldSample() {
+        return mIsStashed && mIsLumaSamplingEnabled;
+    }
+
     protected void updateStashedHandleHintScale() {
         mStashedHandleView.setScaleX(mTaskbarStashedHandleHintScale.value);
         mStashedHandleView.setScaleY(mTaskbarStashedHandleHintScale.value);
@@ -282,7 +302,7 @@
     }
 
     private void updateRegionSamplingWindowVisibility() {
-        mRegionSamplingHelper.setWindowVisible(mIsStashed && !mTaskbarHidden);
+        mRegionSamplingHelper.setWindowVisible(shouldSample() && !mTaskbarHidden);
     }
 
     public boolean isStashedHandleVisible() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index f6703f3..7ad2c68 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -42,7 +42,9 @@
 
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
+import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityOptions;
+import android.app.ActivityTaskManager;
 import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.content.Intent;
@@ -107,6 +109,7 @@
 import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.touch.ItemClickHandler.ItemClickProxy;
+import com.android.launcher3.uioverrides.ApiWrapper;
 import com.android.launcher3.util.ActivityOptionsWrapper;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.DisplayController;
@@ -129,10 +132,13 @@
 import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
 
 import java.io.PrintWriter;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
+import java.util.Set;
 import java.util.function.Consumer;
+import java.util.stream.Collectors;
 
 /**
  * The {@link ActivityContext} with which we inflate Taskbar-related Views. This allows UI elements
@@ -371,6 +377,8 @@
         onSystemBarAttributesChanged(sharedState.systemBarAttrsDisplayId,
                 sharedState.systemBarAttrsBehavior);
         onNavButtonsDarkIntensityChanged(sharedState.navButtonsDarkIntensity);
+        onNavigationBarLumaSamplingEnabled(sharedState.mLumaSamplingDisplayId,
+                sharedState.mIsLumaSamplingEnabled);
 
         if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
             // W/ the flag not set this entire class gets re-created, which resets the value of
@@ -450,6 +458,10 @@
         return mTransientTaskbarBounds;
     }
 
+    protected float getCurrentTaskbarWidth() {
+        return mControllers.taskbarViewController.getCurrentVisualTaskbarWidth();
+    }
+
     @Override
     public StatsLogManager getStatsLogManager() {
         // Used to mock, can't mock a default interface method directly
@@ -835,6 +847,11 @@
                 .updateValue(darkIntensity);
     }
 
+    public void onNavigationBarLumaSamplingEnabled(int displayId, boolean enable) {
+        mControllers.stashedHandleViewController.onNavigationBarLumaSamplingEnabled(displayId,
+                enable);
+    }
+
     /**
      * Called to update a {@link AutohideSuspendFlag} with a new value.
      */
@@ -1056,9 +1073,8 @@
                         } else if (info.isPromise()) {
                             TestLogging.recordEvent(
                                     TestProtocol.SEQUENCE_MAIN, "start: taskbarPromiseIcon");
-                            intent = new PackageManagerHelper(this)
-                                    .getMarketIntent(info.getTargetPackage())
-                                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                            intent = ApiWrapper.getAppMarketActivityIntent(this,
+                                    info.getTargetPackage(), Process.myUserHandle());
                             startActivity(intent);
 
                         } else if (info.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
@@ -1144,18 +1160,28 @@
                 componentKeys,
                 findExactPairMatch,
                 foundTasks -> {
-                    @Nullable Task foundTask = foundTasks.get(0);
+                    @Nullable Task foundTask = foundTasks[0];
                     if (foundTask != null) {
                         TaskView foundTaskView = recents.getTaskViewByTaskId(foundTask.key.id);
-                        if (foundTaskView != null
-                                && foundTaskView.isVisibleToUser()) {
-                            TestLogging.recordEvent(
-                                    TestProtocol.SEQUENCE_MAIN, "start: taskbarAppIcon");
-                            foundTaskView.launchTasks();
-                            return;
+                        if (foundTaskView != null) {
+                            // The foundTaskView contains the 1-2 taskIds we are looking for.
+                            // If we are already in-app and running the correct tasks, no need
+                            // to do anything.
+                            if (FeatureFlags.enableAppPairs()
+                                    && isAlreadyInApp(foundTaskView.getTaskIds())) {
+                                return;
+                            }
+                            // If we are in Overview and the TaskView tile is visible, expand that
+                            // tile.
+                            if (foundTaskView.isVisibleToUser()) {
+                                TestLogging.recordEvent(
+                                        TestProtocol.SEQUENCE_MAIN, "start: taskbarAppIcon");
+                                foundTaskView.launchTasks();
+                                return;
+                            }
                         }
                     }
-
+                    // If none of the above cases apply, launch a new app or app pair.
                     if (findExactPairMatch) {
                         // We did not find the app pair we were looking for, so launch one.
                         recents.getSplitSelectController().getAppPairsController().launchAppPair(
@@ -1167,6 +1193,27 @@
         );
     }
 
+    /**
+     * Checks if a given list of taskIds are all already running in-app.
+     */
+    private boolean isAlreadyInApp(int[] ids) {
+        if (mControllers.uiController.isInOverview()) {
+            return false;
+        }
+
+        RunningTaskInfo[] currentlyRunningTasks = ActivityManagerWrapper.getInstance()
+                .getRunningTasks(false /* filterOnlyVisibleRecents */);
+        Set<Integer> currentlyRunningIds = Arrays.stream(currentlyRunningTasks)
+                .map(task -> task.taskId).collect(Collectors.toSet());
+
+        for (int id : ids) {
+            if (id != ActivityTaskManager.INVALID_TASK_ID && !currentlyRunningIds.contains(id)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     private void startItemInfoActivity(ItemInfo info) {
         Intent intent = new Intent(info.getIntent())
                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -1305,6 +1352,16 @@
         }
     }
 
+    /** Unstashes the Bubble Bar if it is stashed. */
+    @VisibleForTesting
+    public void unstashBubbleBarIfStashed() {
+        mControllers.bubbleControllers.ifPresent(bubbleControllers -> {
+            if (bubbleControllers.bubbleStashController.isStashed()) {
+                bubbleControllers.bubbleStashController.showBubbleBar(false);
+            }
+        });
+    }
+
     protected boolean isUserSetupComplete() {
         return mIsUserSetupComplete;
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
index 30f8d56..e290c3f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
@@ -203,12 +203,7 @@
         val newBackgroundHeight =
             mapRange(progress, backgroundHeightWhileAnimating, maxTransientTaskbarHeight)
         val fullWidth = transientBackgroundBounds.width()
-
-        // .9f is here to restrict min width of the background while animating, so transient
-        // background keeps it pill shape until animation end.
-        val animationWidth =
-            if (DisplayController.isTransientTaskbar(context)) fullWidth.toFloat() * .9f
-            else fullWidth.toFloat()
+        val animationWidth = context.currentTaskbarWidth
         val backgroundWidthWhileAnimating =
             if (isAnimatingPinning) animationWidth else stashedHandleWidth.toFloat()
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt
index 3f9b66a..dab9950 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt
@@ -16,7 +16,6 @@
 package com.android.launcher3.taskbar
 
 import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
 import android.animation.AnimatorSet
 import android.animation.ObjectAnimator
 import android.annotation.SuppressLint
@@ -197,32 +196,19 @@
             mActivityContext.deviceProfile.taskbarIconSize) / 2 + verticalOffsetForPopupView
     }
 
-    override fun animateClose() {
-        if (!mIsOpen) {
-            return
+    override fun onCreateCloseAnimation(anim: AnimatorSet?) {
+        // If taskbar pinning preference changed insert custom close animation for popup menu.
+        if (didPreferenceChange) {
+            mOpenCloseAnimator = getCloseAnimator()
         }
-        if (mOpenCloseAnimator != null) {
-            mOpenCloseAnimator.cancel()
-        }
-        mIsOpen = false
-
-        mOpenCloseAnimator = getCloseAnimator()
-
-        mOpenCloseAnimator.addListener(
-            object : AnimatorListenerAdapter() {
-                override fun onAnimationEnd(animation: Animator) {
-                    mOpenCloseAnimator = null
-                    if (mDeferContainerRemoval) {
-                        setVisibility(INVISIBLE)
-                    } else {
-                        closeComplete()
-                    }
-                }
-            }
-        )
         onCloseCallback(didPreferenceChange)
         onCloseCallback = {}
-        mOpenCloseAnimator.start()
+    }
+
+    /** Aligning the view pivot to center for animation. */
+    override fun setPivotForOpenCloseAnimation() {
+        pivotX = measuredWidth / 2f
+        pivotY = measuredHeight.toFloat()
     }
 
     private fun getCloseAnimator(): AnimatorSet {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index 6ddf9e9..faa67be 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -23,6 +23,7 @@
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SEARCH_ACTION;
 import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.EXTENDED_CONTAINERS;
 import static com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers.ContainerCase.DEVICE_SEARCH_RESULT_CONTAINER;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_DRAGDROP;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -517,6 +518,9 @@
                 // Note, this must be done last to ensure no AutohideSuspendFlags are active, as
                 // that will prevent us from stashing until the timeout.
                 mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
+
+                mActivity.getStatsLogManager().logger().withItemInfo(mDragObject.dragInfo)
+                        .log(LAUNCHER_APP_LAUNCH_DRAGDROP);
             }
         }
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
index 1a34b7a..7ebc18d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
@@ -101,8 +101,10 @@
 
         windowLayoutParams.providedInsets =
             if (enableTaskbarNoRecreate()) {
-                getProvidedInsets(controllers.sharedState!!.insetsFrameProviders!!,
-                        insetsRoundedCornerFlag)
+                getProvidedInsets(
+                    controllers.sharedState!!.insetsFrameProviders!!,
+                    insetsRoundedCornerFlag
+                )
             } else {
                 getProvidedInsets(insetsRoundedCornerFlag)
             }
@@ -164,19 +166,18 @@
 
     /**
      * This is for when ENABLE_TASKBAR_NO_RECREATION is enabled. We generate one instance of
-     * providedInsets and use it across the entire lifecycle of TaskbarManager. The only thing
-     * we need to reset is nav bar flags based on insetsRoundedCornerFlag.
+     * providedInsets and use it across the entire lifecycle of TaskbarManager. The only thing we
+     * need to reset is nav bar flags based on insetsRoundedCornerFlag.
      */
-    private fun getProvidedInsets(providedInsets: Array<InsetsFrameProvider>,
-                                  insetsRoundedCornerFlag: Int): Array<InsetsFrameProvider> {
+    private fun getProvidedInsets(
+        providedInsets: Array<InsetsFrameProvider>,
+        insetsRoundedCornerFlag: Int
+    ): Array<InsetsFrameProvider> {
         val navBarsFlag =
-                (if (context.isGestureNav) FLAG_SUPPRESS_SCRIM else 0) or insetsRoundedCornerFlag
+            (if (context.isGestureNav) FLAG_SUPPRESS_SCRIM else 0) or insetsRoundedCornerFlag
         for (provider in providedInsets) {
             if (provider.type == navigationBars()) {
-                provider.setFlags(
-                        navBarsFlag,
-                        FLAG_SUPPRESS_SCRIM or FLAG_INSETS_ROUNDED_CORNER
-                )
+                provider.setFlags(navBarsFlag, FLAG_SUPPRESS_SCRIM or FLAG_INSETS_ROUNDED_CORNER)
             }
         }
         return providedInsets
@@ -184,25 +185,22 @@
 
     /**
      * The inset types and number of insets provided have to match for both gesture nav and button
-     * nav. The values and the order of the elements in array are allowed to differ.
-     * Reason being WM does not allow types and number of insets changing for a given window once it
-     * is added into the hierarchy for performance reasons.
+     * nav. The values and the order of the elements in array are allowed to differ. Reason being WM
+     * does not allow types and number of insets changing for a given window once it is added into
+     * the hierarchy for performance reasons.
      */
     private fun getProvidedInsets(insetsRoundedCornerFlag: Int): Array<InsetsFrameProvider> {
         val navBarsFlag =
-                (if (context.isGestureNav) FLAG_SUPPRESS_SCRIM else 0) or insetsRoundedCornerFlag
+            (if (context.isGestureNav) FLAG_SUPPRESS_SCRIM else 0) or insetsRoundedCornerFlag
         return arrayOf(
-                InsetsFrameProvider(insetsOwner, 0, navigationBars())
-                        .setFlags(
-                                navBarsFlag,
-                                FLAG_SUPPRESS_SCRIM or FLAG_INSETS_ROUNDED_CORNER
-                        ),
-                InsetsFrameProvider(insetsOwner, 0, tappableElement()),
-                InsetsFrameProvider(insetsOwner, 0, mandatorySystemGestures()),
-                InsetsFrameProvider(insetsOwner, INDEX_LEFT, systemGestures())
-                        .setSource(SOURCE_DISPLAY),
-                InsetsFrameProvider(insetsOwner, INDEX_RIGHT, systemGestures())
-                        .setSource(SOURCE_DISPLAY)
+            InsetsFrameProvider(insetsOwner, 0, navigationBars())
+                .setFlags(navBarsFlag, FLAG_SUPPRESS_SCRIM or FLAG_INSETS_ROUNDED_CORNER),
+            InsetsFrameProvider(insetsOwner, 0, tappableElement()),
+            InsetsFrameProvider(insetsOwner, 0, mandatorySystemGestures()),
+            InsetsFrameProvider(insetsOwner, INDEX_LEFT, systemGestures())
+                .setSource(SOURCE_DISPLAY),
+            InsetsFrameProvider(insetsOwner, INDEX_RIGHT, systemGestures())
+                .setSource(SOURCE_DISPLAY)
         )
     }
 
@@ -216,46 +214,52 @@
             provider.insetsSize = getInsetsForGravity(tappableHeight, gravity)
         } else if (provider.type == systemGestures() && provider.index == INDEX_LEFT) {
             val leftIndexInset =
-                    if (context.isThreeButtonNav) 0
-                    else gestureNavSettingsObserver.getLeftSensitivityForCallingUser(res)
+                if (context.isThreeButtonNav) 0
+                else gestureNavSettingsObserver.getLeftSensitivityForCallingUser(res)
             provider.insetsSize = Insets.of(leftIndexInset, 0, 0, 0)
         } else if (provider.type == systemGestures() && provider.index == INDEX_RIGHT) {
             val rightIndexInset =
-                    if (context.isThreeButtonNav) 0
-                    else gestureNavSettingsObserver.getRightSensitivityForCallingUser(res)
+                if (context.isThreeButtonNav) 0
+                else gestureNavSettingsObserver.getRightSensitivityForCallingUser(res)
             provider.insetsSize = Insets.of(0, 0, rightIndexInset, 0)
         }
 
         // When in gesture nav, report the stashed height to the IME, to allow hiding the
         // IME navigation bar.
-        val imeInsetsSize = if (ENABLE_HIDE_IME_CAPTION_BAR && context.isGestureNav) {
-            getInsetsForGravity(controllers.taskbarStashController.stashedHeight, gravity);
-        } else {
-            getInsetsForGravity(taskbarHeightForIme, gravity)
-        }
+        val imeInsetsSize =
+            if (ENABLE_HIDE_IME_CAPTION_BAR && context.isGestureNav) {
+                getInsetsForGravity(controllers.taskbarStashController.stashedHeight, gravity)
+            } else {
+                getInsetsForGravity(taskbarHeightForIme, gravity)
+            }
         val imeInsetsSizeOverride =
-                arrayOf(
-                        InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, imeInsetsSize),
-                        InsetsFrameProvider.InsetsSizeOverride(TYPE_VOICE_INTERACTION,
-                                // No-op override to keep the size and types in sync with the
-                                // override below (insetsSizeOverrides must have the same length and
-                                // types after the window is added according to
-                                // WindowManagerService#relayoutWindow)
-                                provider.insetsSize)
+            arrayOf(
+                InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, imeInsetsSize),
+                InsetsFrameProvider.InsetsSizeOverride(
+                    TYPE_VOICE_INTERACTION,
+                    // No-op override to keep the size and types in sync with the
+                    // override below (insetsSizeOverrides must have the same length and
+                    // types after the window is added according to
+                    // WindowManagerService#relayoutWindow)
+                    provider.insetsSize
                 )
+            )
         // Use 0 tappableElement insets for the VoiceInteractionWindow when gesture nav is enabled.
         val visInsetsSizeForTappableElement =
-                if (context.isGestureNav) getInsetsForGravity(0, gravity)
-                else getInsetsForGravity(tappableHeight, gravity)
+            if (context.isGestureNav) getInsetsForGravity(0, gravity)
+            else getInsetsForGravity(tappableHeight, gravity)
         val insetsSizeOverrideForTappableElement =
-                arrayOf(
-                        InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, imeInsetsSize),
-                        InsetsFrameProvider.InsetsSizeOverride(TYPE_VOICE_INTERACTION,
-                                visInsetsSizeForTappableElement
-                        ),
-                )
-        if ((context.isGestureNav || ENABLE_TASKBAR_NAVBAR_UNIFICATION)
-                && provider.type == tappableElement()) {
+            arrayOf(
+                InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, imeInsetsSize),
+                InsetsFrameProvider.InsetsSizeOverride(
+                    TYPE_VOICE_INTERACTION,
+                    visInsetsSizeForTappableElement
+                ),
+            )
+        if (
+            (context.isGestureNav || ENABLE_TASKBAR_NAVBAR_UNIFICATION) &&
+                provider.type == tappableElement()
+        ) {
             provider.insetsSizeOverrides = insetsSizeOverrideForTappableElement
         } else if (provider.type != systemGestures()) {
             // We only override insets at the bottom of the screen
@@ -264,8 +268,8 @@
     }
 
     /**
-     * @return [Insets] where the [inset] is either used as a bottom inset or
-     * right/left inset if using 3 button nav
+     * @return [Insets] where the [inset] is either used as a bottom inset or right/left inset if
+     *   using 3 button nav
      */
     private fun getInsetsForGravity(inset: Int, gravity: Int): Insets {
         if ((gravity and Gravity.BOTTOM) == Gravity.BOTTOM) {
@@ -277,7 +281,7 @@
         val isSeascape = (gravity and Gravity.START) == Gravity.START
         val leftInset = if (isSeascape) inset else 0
         val rightInset = if (isSeascape) 0 else inset
-        return Insets.of(leftInset , 0, rightInset, 0)
+        return Insets.of(leftInset, 0, rightInset, 0)
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 831bc11..33641a4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -25,6 +25,9 @@
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
 import static com.android.launcher3.config.FeatureFlags.enableTaskbarNoRecreate;
+import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
+import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
+import static com.android.launcher3.util.DisplayController.CHANGE_TASKBAR_PINNING;
 import static com.android.launcher3.util.DisplayController.TASKBAR_NOT_DESTROYED_TAG;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
@@ -55,7 +58,6 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.statemanager.StatefulActivity;
@@ -71,7 +73,6 @@
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
 import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
-import com.android.wm.shell.Flags;
 
 import java.io.PrintWriter;
 import java.util.StringJoiner;
@@ -135,7 +136,17 @@
      * We use WindowManager's ComponentCallbacks() for internal UI changes (similar to an Activity)
      * which comes via a different channel
      */
-    private final OnIDPChangeListener mIdpChangeListener = c -> recreateTaskbar();
+    private final RecreationListener mRecreationListener = new RecreationListener();
+
+    private class RecreationListener implements DisplayController.DisplayInfoChangeListener {
+        @Override
+        public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) {
+            if ((flags & (CHANGE_DENSITY | CHANGE_NAVIGATION_MODE
+                    | CHANGE_TASKBAR_PINNING)) != 0) {
+                recreateTaskbar();
+            }
+        }
+    }
     private final SettingsCache.OnChangeListener mOnSettingsChangeListener = c -> recreateTaskbar();
 
     private boolean mUserUnlocked = false;
@@ -329,7 +340,10 @@
             return;
         }
 
-        if (mActivity != null && mActivity.isResumed() && !mActivity.isInState(OVERVIEW)) {
+        if (mActivity != null
+                && mActivity.isResumed()
+                && !mActivity.isInState(OVERVIEW)
+                && !(mActivity instanceof QuickstepLauncher l && l.areFreeformTasksVisible())) {
             mContext.startActivity(homeAllAppsIntent);
             return;
         }
@@ -353,7 +367,7 @@
      */
     public void onUserUnlocked() {
         mUserUnlocked = true;
-        LauncherAppState.getIDP(mContext).addOnChangeListener(mIdpChangeListener);
+        DisplayController.INSTANCE.get(mContext).addChangeListener(mRecreationListener);
         recreateTaskbar();
         addTaskbarRootViewToWindow();
     }
@@ -526,6 +540,14 @@
         }
     }
 
+    public void onNavigationBarLumaSamplingEnabled(int displayId, boolean enable) {
+        mSharedState.mLumaSamplingDisplayId = displayId;
+        mSharedState.mIsLumaSamplingEnabled = enable;
+        if (mTaskbarActivityContext != null) {
+            mTaskbarActivityContext.onNavigationBarLumaSamplingEnabled(displayId, enable);
+        }
+    }
+
     private void removeActivityCallbacksAndListeners() {
         if (mActivity != null) {
             mActivity.removeOnDeviceProfileChangeListener(mDebugActivityDeviceProfileChanged);
@@ -550,8 +572,9 @@
         UI_HELPER_EXECUTOR.execute(
                 () -> mTaskbarBroadcastReceiver.unregisterReceiverSafely(mContext));
         destroyExistingTaskbar();
+        removeTaskbarRootViewFromWindow();
         if (mUserUnlocked) {
-            LauncherAppState.getIDP(mContext).removeOnChangeListener(mIdpChangeListener);
+            DisplayController.INSTANCE.get(mContext).removeChangeListener(mRecreationListener);
         }
         SettingsCache.INSTANCE.get(mContext)
                 .unregister(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt
index 6cb28ee..5b117fc 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt
@@ -20,6 +20,7 @@
 import android.view.View
 import androidx.annotation.VisibleForTesting
 import androidx.core.animation.doOnEnd
+import com.android.app.animation.Interpolators
 import com.android.launcher3.LauncherPrefs
 import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING
 import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_DIVIDER_MENU_CLOSE
@@ -106,6 +107,7 @@
             taskbarViewController.taskbarIconTranslationXForPinning.animateToValue(animateToValue)
         )
 
+        animatorSet.interpolator = Interpolators.EMPHASIZED
         return animatorSet
     }
 
@@ -129,6 +131,6 @@
     companion object {
         const val PINNING_PERSISTENT = 1f
         const val PINNING_TRANSIENT = 0f
-        const val PINNING_ANIMATION_DURATION = 500L
+        const val PINNING_ANIMATION_DURATION = 600L
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java
index d09f74c..41b777b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java
@@ -52,6 +52,10 @@
     // TaskbarManager#onNavButtonsDarkIntensityChanged()
     public float navButtonsDarkIntensity;
 
+    // TaskbarManager#onNavigationBarLumaSamplingEnabled()
+    public int mLumaSamplingDisplayId;
+    public boolean mIsLumaSamplingEnabled;
+
     public boolean setupUIVisible = false;
 
     public boolean allAppsVisible = false;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index b3a8b93..eced202 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -590,6 +590,7 @@
             mAnimator.addListener(AnimatorListeners.forEndCallback(() -> {
                 mAnimator = null;
                 mIsStashed = isStashed;
+                onIsStashedChanged();
             }));
             return;
         }
@@ -604,7 +605,7 @@
             @Override
             public void onAnimationStart(Animator animation) {
                 mIsStashed = isStashed;
-                onIsStashedChanged(mIsStashed);
+                onIsStashedChanged();
 
                 cancelTimeoutIfExists();
             }
@@ -829,9 +830,9 @@
                 .setDuration(TASKBAR_HINT_STASH_DURATION).start();
     }
 
-    private void onIsStashedChanged(boolean isStashed) {
+    private void onIsStashedChanged() {
         mControllers.runAfterInit(() -> {
-            mControllers.stashedHandleViewController.onIsStashedChanged(isStashed);
+            mControllers.stashedHandleViewController.onIsStashedChanged();
             mControllers.taskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged();
         });
     }
@@ -930,19 +931,26 @@
     }
 
     /**
-     * We stash when IME or IME switcher is showing AND NOT
-     *  * in small screen AND
-     *  * 3 button nav AND
-     *  * landscape (or seascape)
-     * We do not stash if taskbar is transient or hardware keyboard is active.
+     * We stash when IME or IME switcher is showing.
+     *
+     * <p>Do not stash if in small screen, with 3 button nav, and in landscape (or seascape).
+     * <p>Do not stash if taskbar is transient.
+     * <p>Do not stash if hardware keyboard is attached and taskbar is pinned.
      */
     private boolean shouldStashForIme() {
-        if (DisplayController.isTransientTaskbar(mActivity) || mActivity.isHardwareKeyboard()) {
+        if (DisplayController.isTransientTaskbar(mActivity)) {
             return false;
         }
-        return (mIsImeShowing || mIsImeSwitcherShowing) &&
-                !(mActivity.isPhoneMode() && mActivity.isThreeButtonNav()
-                        && mActivity.getDeviceProfile().isLandscape);
+        // Do not stash if in small screen, with 3 button nav, and in landscape.
+        if (mActivity.isPhoneMode() && mActivity.isThreeButtonNav()
+                && mActivity.getDeviceProfile().isLandscape) {
+            return false;
+        }
+        // Do not stash if pinned taskbar and hardware keyboard is attached.
+        if (mActivity.isHardwareKeyboard() && DisplayController.isPinnedTaskbar(mActivity)) {
+            return false;
+        }
+        return mIsImeShowing || mIsImeSwitcherShowing;
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index df2a43b..ecedf8a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -25,6 +25,7 @@
 import android.graphics.drawable.BitmapDrawable;
 import android.view.MotionEvent;
 import android.view.View;
+import android.window.RemoteTransition;
 
 import androidx.annotation.CallSuper;
 import androidx.annotation.NonNull;
@@ -219,7 +220,7 @@
                 Collections.singletonList(splitSelectSource.itemInfo.getComponentKey()),
                 false /* findExactPairMatch */,
                 foundTasks -> {
-                    @Nullable Task foundTask = foundTasks.get(0);
+                    @Nullable Task foundTask = foundTasks[0];
                     splitSelectSource.alreadyRunningTaskId = foundTask == null
                             ? INVALID_TASK_ID
                             : foundTask.key.id;
@@ -238,7 +239,7 @@
                 Collections.singletonList(info.getComponentKey()),
                 false /* findExactPairMatch */,
                 foundTasks -> {
-                    @Nullable Task foundTask = foundTasks.get(0);
+                    @Nullable Task foundTask = foundTasks[0];
                     if (foundTask != null) {
                         TaskView foundTaskView = recents.getTaskViewByTaskId(foundTask.key.id);
                         // TODO (b/266482558): This additional null check is needed because there
@@ -302,7 +303,8 @@
     /**
      * Launches the given task in split-screen.
      */
-    public void launchSplitTasks(@NonNull GroupTask groupTask) { }
+    public void launchSplitTasks(
+            @NonNull GroupTask groupTask, @Nullable RemoteTransition remoteTransition) { }
 
     /**
      * Returns the matching view (if any) in the taskbar.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 33fb395..1f03f24 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -159,6 +159,8 @@
     private final int mTransientIconSize;
     private final int mPersistentIconSize;
 
+    private final float mTaskbarLeftRightMargin;
+
     public TaskbarViewController(TaskbarActivityContext activity, TaskbarView taskbarView) {
         mActivity = activity;
         mTransientTaskbarDp = mActivity.getTransientTaskbarDeviceProfile();
@@ -184,6 +186,9 @@
             mTaskbarThemedIconsBackgroundColor = ColorUtils.HSLToColor(colorHSL);
         }
         mIsRtl = Utilities.isRtl(mTaskbarView.getResources());
+        mTaskbarLeftRightMargin = mActivity.getResources().getDimensionPixelSize(
+                R.dimen.transient_taskbar_padding);
+
     }
 
     public void init(TaskbarControllers controllers) {
@@ -391,6 +396,26 @@
         }
     }
 
+    /**
+     * Calculates visual taskbar view width.
+     */
+    public float getCurrentVisualTaskbarWidth() {
+        if (mTaskbarView.getIconViews().length == 0) {
+            return 0;
+        }
+
+        View[] iconViews = mTaskbarView.getIconViews();
+
+        int leftIndex = mActivity.getDeviceProfile().isQsbInline && !mIsRtl ? 1 : 0;
+        int rightIndex = mActivity.getDeviceProfile().isQsbInline && mIsRtl
+                ? iconViews.length - 2
+                : iconViews.length - 1;
+
+        float left = iconViews[leftIndex].getX();
+        float right = iconViews[rightIndex].getRight() + iconViews[rightIndex].getTranslationX();
+
+        return right - left + (2 * mTaskbarLeftRightMargin);
+    }
 
     /**
      * Sets the translation of the TaskbarView during the swipe up gesture.
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
index b516d6f..23e3310 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
@@ -65,18 +65,19 @@
 
     fun getParamsToCenterView(): FrameLayout.LayoutParams {
         val params = FrameLayout.LayoutParams(
-                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
+                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
         params.gravity = Gravity.CENTER
         return params;
     }
 
-    open fun repositionContextualContainer(contextualContainer: ViewGroup, barAxisMargin: Int,
+    open fun repositionContextualContainer(contextualContainer: ViewGroup, buttonSize: Int,
+                                           barAxisMarginStart: Int, barAxisMarginEnd: Int,
                                            gravity: Int) {
         val contextualContainerParams = FrameLayout.LayoutParams(
-                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)
+                buttonSize, ViewGroup.LayoutParams.MATCH_PARENT)
         contextualContainerParams.apply {
-            marginStart = barAxisMargin
-            marginEnd = barAxisMargin
+            marginStart = barAxisMarginStart
+            marginEnd = barAxisMarginEnd
             topMargin = 0
             bottomMargin = 0
         }
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
index 65b77ac..f31af09 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
@@ -21,6 +21,7 @@
 import android.graphics.drawable.PaintDrawable
 import android.view.Gravity
 import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
 import android.widget.FrameLayout
 import android.widget.ImageView
 import android.widget.LinearLayout
@@ -103,8 +104,9 @@
 
         val contextualMargin = resources.getDimensionPixelSize(
                 R.dimen.taskbar_contextual_button_padding)
-        repositionContextualContainer(endContextualContainer, 0, Gravity.END)
-        repositionContextualContainer(startContextualContainer, contextualMargin, Gravity.START)
+        repositionContextualContainer(endContextualContainer, WRAP_CONTENT, 0, 0, Gravity.END)
+        repositionContextualContainer(startContextualContainer, WRAP_CONTENT, contextualMargin,
+                contextualMargin, Gravity.START)
 
         if (imeSwitcher != null) {
             startContextualContainer.addView(imeSwitcher)
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
index 7583cc1..b1b50d6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
@@ -22,10 +22,8 @@
 import android.widget.FrameLayout
 import android.widget.ImageView
 import android.widget.LinearLayout
-import androidx.core.view.children
 import com.android.launcher3.R
 import com.android.launcher3.taskbar.TaskbarActivityContext
-import com.android.launcher3.util.DimensionUtils
 import com.android.systemui.shared.rotation.RotationButton
 
 open class PhoneLandscapeNavLayoutter(
@@ -48,48 +46,67 @@
     ) {
 
     override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
-        // TODO(b/230395757): Polish pending, this is just to make it usable
-        val endStartMargins = resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size)
-        val taskbarDimensions = DimensionUtils.getTaskbarPhoneDimensions(context.deviceProfile,
-                resources, context.isPhoneMode)
-        navButtonContainer.removeAllViews()
-        navButtonContainer.orientation = LinearLayout.VERTICAL
+        val totalHeight = context.deviceProfile.heightPx
+        val homeButtonHeight = resources.getDimensionPixelSize(
+                R.dimen.taskbar_phone_home_button_size)
+        val roundedCornerContentMargin = resources.getDimensionPixelSize(
+                R.dimen.taskbar_phone_rounded_corner_content_margin)
+        val contentPadding = resources.getDimensionPixelSize(R.dimen.taskbar_phone_content_padding)
+        val contentWidth = totalHeight - roundedCornerContentMargin * 2 - contentPadding * 2
+
+        // left:back:space(reserved for home):overview:right = 0.25:0.5:1:0.5:0.25
+        val contextualButtonHeight = contentWidth / (0.25f + 0.5f + 1f + 0.5f + 0.25f) * 0.25f
+        val sideButtonHeight = contextualButtonHeight * 2
+        val navButtonContainerHeight = contentWidth - contextualButtonHeight * 2
 
         val navContainerParams = FrameLayout.LayoutParams(
-                taskbarDimensions.x, ViewGroup.LayoutParams.MATCH_PARENT)
+            ViewGroup.LayoutParams.MATCH_PARENT, navButtonContainerHeight.toInt())
         navContainerParams.apply {
-            topMargin = endStartMargins
-            bottomMargin = endStartMargins
+            topMargin =
+                    (contextualButtonHeight + contentPadding + roundedCornerContentMargin).toInt()
+            bottomMargin =
+                    (contextualButtonHeight + contentPadding + roundedCornerContentMargin).toInt()
             marginEnd = 0
             marginStart = 0
         }
 
-        navButtonContainer.layoutParams = navContainerParams
-        navButtonContainer.gravity = Gravity.CENTER
+        // Ensure order of buttons is correct
+        navButtonContainer.removeAllViews()
+        navButtonContainer.orientation = LinearLayout.VERTICAL
 
         addThreeButtons()
 
+        navButtonContainer.layoutParams = navContainerParams
+        navButtonContainer.gravity = Gravity.CENTER
+
         // Add the spaces in between the nav buttons
-        val spaceInBetween: Int =
-            resources.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween_phone)
-        navButtonContainer.children.forEachIndexed { i, navButton ->
+        val spaceInBetween = (navButtonContainerHeight - homeButtonHeight -
+                sideButtonHeight * 2) / 2.0f
+        for (i in 0 until navButtonContainer.childCount) {
+            val navButton = navButtonContainer.getChildAt(i)
             val buttonLayoutParams = navButton.layoutParams as LinearLayout.LayoutParams
-            buttonLayoutParams.weight = 1f
+            val margin = (spaceInBetween / 2).toInt()
             when (i) {
                 0 -> {
-                    buttonLayoutParams.bottomMargin = spaceInBetween / 2
+                    // First button
+                    buttonLayoutParams.bottomMargin = margin
+                    buttonLayoutParams.height = sideButtonHeight.toInt()
                 }
                 navButtonContainer.childCount - 1 -> {
-                    buttonLayoutParams.topMargin = spaceInBetween / 2
+                    // Last button
+                    buttonLayoutParams.topMargin = margin
+                    buttonLayoutParams.height = sideButtonHeight.toInt()
                 }
                 else -> {
-                    buttonLayoutParams.bottomMargin = spaceInBetween / 2
-                    buttonLayoutParams.topMargin = spaceInBetween / 2
+                    // other buttons
+                    buttonLayoutParams.topMargin = margin
+                    buttonLayoutParams.bottomMargin = margin
+                    buttonLayoutParams.height = homeButtonHeight
                 }
             }
         }
 
-        repositionContextualButtons()
+        repositionContextualButtons(contextualButtonHeight.toInt())
     }
 
     open fun addThreeButtons() {
@@ -99,13 +116,15 @@
         navButtonContainer.addView(backButton)
     }
 
-    open fun repositionContextualButtons() {
+    open fun repositionContextualButtons(buttonSize: Int) {
         endContextualContainer.removeAllViews()
         startContextualContainer.removeAllViews()
 
-        val contextualMargin = resources.getDimensionPixelSize(
-                R.dimen.taskbar_contextual_button_padding)
-        repositionContextualContainer(startContextualContainer, contextualMargin, Gravity.TOP)
+        val roundedCornerContentMargin = resources.getDimensionPixelSize(
+                R.dimen.taskbar_phone_rounded_corner_content_margin)
+        val contentPadding = resources.getDimensionPixelSize(R.dimen.taskbar_phone_content_padding)
+        repositionContextualContainer(startContextualContainer, buttonSize,
+                roundedCornerContentMargin + contentPadding, 0, Gravity.TOP)
 
         if (imeSwitcher != null) {
             startContextualContainer.addView(imeSwitcher)
@@ -120,15 +139,16 @@
         }
     }
 
-    override fun repositionContextualContainer(contextualContainer: ViewGroup, barAxisMargin: Int,
+    override fun repositionContextualContainer(contextualContainer: ViewGroup, buttonSize: Int,
+                                               barAxisMarginTop: Int, barAxisMarginBottom: Int,
                                                gravity: Int) {
         val contextualContainerParams = FrameLayout.LayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
+                ViewGroup.LayoutParams.MATCH_PARENT, buttonSize)
         contextualContainerParams.apply {
             marginStart = 0
             marginEnd = 0
-            topMargin = barAxisMargin
-            bottomMargin = barAxisMargin
+            topMargin = barAxisMarginTop
+            bottomMargin = barAxisMarginBottom
         }
         contextualContainerParams.gravity = gravity or Gravity.CENTER_HORIZONTAL
         contextualContainer.layoutParams = contextualContainerParams
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
index 4388ce6..05183b8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
@@ -24,7 +24,6 @@
 import android.widget.LinearLayout
 import com.android.launcher3.R
 import com.android.launcher3.taskbar.TaskbarActivityContext
-import com.android.launcher3.util.DimensionUtils
 import com.android.systemui.shared.rotation.RotationButton
 
 class PhonePortraitNavLayoutter(
@@ -47,26 +46,33 @@
     ) {
 
     override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
-        // TODO(b/230395757): Polish pending, this is just to make it usable
-        val taskbarDimensions =
-            DimensionUtils.getTaskbarPhoneDimensions(context.deviceProfile, resources,
-                    context.isPhoneMode)
-        val endStartMargins = resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size)
+        val totalWidth = context.deviceProfile.widthPx
+        val homeButtonWidth = resources.getDimensionPixelSize(R.dimen.taskbar_phone_home_button_size)
+        val roundedCornerContentMargin = resources.getDimensionPixelSize(
+                R.dimen.taskbar_phone_rounded_corner_content_margin)
+        val contentPadding = resources.getDimensionPixelSize(R.dimen.taskbar_phone_content_padding)
+        val contentWidth = totalWidth - roundedCornerContentMargin * 2 - contentPadding * 2
+
+        // left:back:space(reserved for home):overview:right = 0.25:0.5:1:0.5:0.25
+        val contextualButtonWidth = contentWidth / (0.25f + 0.5f + 1f + 0.5f + 0.25f) * 0.25f
+        val sideButtonWidth = contextualButtonWidth * 2
+        val navButtonContainerWidth = contentWidth - contextualButtonWidth * 2
+
+        val navContainerParams = FrameLayout.LayoutParams(navButtonContainerWidth.toInt(),
+                ViewGroup.LayoutParams.MATCH_PARENT)
+        navContainerParams.apply {
+            topMargin = 0
+            bottomMargin = 0
+            marginEnd =
+                    (contextualButtonWidth + contentPadding + roundedCornerContentMargin).toInt()
+            marginStart =
+                    (contextualButtonWidth + contentPadding + roundedCornerContentMargin).toInt()
+        }
 
         // Ensure order of buttons is correct
         navButtonContainer.removeAllViews()
         navButtonContainer.orientation = LinearLayout.HORIZONTAL
 
-        val navContainerParams = FrameLayout.LayoutParams(
-                taskbarDimensions.x, ViewGroup.LayoutParams.MATCH_PARENT)
-        navContainerParams.apply {
-            topMargin = 0
-            bottomMargin = 0
-            marginEnd = endStartMargins
-            marginStart = endStartMargins
-        }
-
-        // Swap recents and back button in case we were landscape prior to this
         navButtonContainer.addView(backButton)
         navButtonContainer.addView(homeButton)
         navButtonContainer.addView(recentsButton)
@@ -75,25 +81,28 @@
         navButtonContainer.gravity = Gravity.CENTER
 
         // Add the spaces in between the nav buttons
-        val spaceInBetween =
-            resources.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween_phone)
+        val spaceInBetween = (navButtonContainerWidth - homeButtonWidth -
+                sideButtonWidth * 2) / 2.0f
         for (i in 0 until navButtonContainer.childCount) {
             val navButton = navButtonContainer.getChildAt(i)
             val buttonLayoutParams = navButton.layoutParams as LinearLayout.LayoutParams
-            buttonLayoutParams.weight = 1f
+            val margin = (spaceInBetween / 2).toInt()
             when (i) {
                 0 -> {
                     // First button
-                    buttonLayoutParams.marginEnd = spaceInBetween / 2
+                    buttonLayoutParams.marginEnd = margin
+                    buttonLayoutParams.width = sideButtonWidth.toInt()
                 }
                 navButtonContainer.childCount - 1 -> {
                     // Last button
-                    buttonLayoutParams.marginStart = spaceInBetween / 2
+                    buttonLayoutParams.marginStart = margin
+                    buttonLayoutParams.width = sideButtonWidth.toInt()
                 }
                 else -> {
                     // other buttons
-                    buttonLayoutParams.marginStart = spaceInBetween / 2
-                    buttonLayoutParams.marginEnd = spaceInBetween / 2
+                    buttonLayoutParams.marginStart = margin
+                    buttonLayoutParams.marginEnd = margin
+                    buttonLayoutParams.width = homeButtonWidth
                 }
             }
         }
@@ -101,9 +110,8 @@
         endContextualContainer.removeAllViews()
         startContextualContainer.removeAllViews()
 
-        val contextualMargin = resources.getDimensionPixelSize(
-                R.dimen.taskbar_contextual_button_padding)
-        repositionContextualContainer(endContextualContainer, contextualMargin, Gravity.END)
+        repositionContextualContainer(endContextualContainer, contextualButtonWidth.toInt(), 0,
+                roundedCornerContentMargin + contentPadding, Gravity.END)
 
         if (imeSwitcher != null) {
             endContextualContainer.addView(imeSwitcher)
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt
index 0368b1d..0f52552 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt
@@ -50,13 +50,15 @@
         navButtonContainer.addView(recentsButton)
     }
 
-    override fun repositionContextualButtons() {
+    override fun repositionContextualButtons(buttonSize: Int) {
         endContextualContainer.removeAllViews()
         startContextualContainer.removeAllViews()
 
-        val contextualMargin = resources.getDimensionPixelSize(
-                R.dimen.taskbar_contextual_button_padding)
-        repositionContextualContainer(endContextualContainer, contextualMargin, Gravity.BOTTOM)
+        val roundedCornerContentMargin = resources.getDimensionPixelSize(
+                R.dimen.taskbar_phone_rounded_corner_content_margin)
+        val contentPadding = resources.getDimensionPixelSize(R.dimen.taskbar_phone_content_padding)
+        repositionContextualContainer(endContextualContainer, buttonSize, 0,
+                roundedCornerContentMargin + contentPadding,  Gravity.BOTTOM)
 
         if (imeSwitcher != null) {
             endContextualContainer.addView(imeSwitcher)
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
index 1ac0060..5111bba 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
@@ -19,6 +19,7 @@
 import android.content.res.Resources
 import android.view.Gravity
 import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
 import android.widget.FrameLayout
 import android.widget.ImageView
 import android.widget.LinearLayout
@@ -61,8 +62,9 @@
 
         val contextualMargin = resources.getDimensionPixelSize(
                 R.dimen.taskbar_contextual_button_padding)
-        repositionContextualContainer(endContextualContainer, 0, Gravity.END)
-        repositionContextualContainer(startContextualContainer, contextualMargin, Gravity.START)
+        repositionContextualContainer(endContextualContainer, WRAP_CONTENT, 0, 0, Gravity.END)
+        repositionContextualContainer(startContextualContainer, WRAP_CONTENT, contextualMargin,
+                contextualMargin, Gravity.START)
 
         if (imeSwitcher != null) {
             startContextualContainer.addView(imeSwitcher)
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
index 5465b6b..45dbebb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
@@ -19,6 +19,7 @@
 import android.content.res.Resources
 import android.view.Gravity
 import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
 import android.widget.FrameLayout
 import android.widget.ImageView
 import android.widget.LinearLayout
@@ -96,8 +97,9 @@
         if (!context.deviceProfile.isGestureMode) {
             val contextualMargin = resources.getDimensionPixelSize(
                     R.dimen.taskbar_contextual_button_padding)
-            repositionContextualContainer(endContextualContainer, 0, Gravity.END)
-            repositionContextualContainer(startContextualContainer, contextualMargin, Gravity.START)
+            repositionContextualContainer(endContextualContainer, WRAP_CONTENT, 0, 0, Gravity.END)
+            repositionContextualContainer(startContextualContainer, WRAP_CONTENT, contextualMargin,
+                    contextualMargin, Gravity.START)
 
             if (imeSwitcher != null) {
                 startContextualContainer.addView(imeSwitcher)
diff --git a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
index 4a26559..784c560 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -17,13 +17,16 @@
 package com.android.launcher3.uioverrides;
 
 import android.app.ActivityOptions;
+import android.app.PendingIntent;
 import android.app.Person;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
 import android.content.pm.LauncherUserInfo;
 import android.content.pm.ShortcutInfo;
 import android.graphics.drawable.ColorDrawable;
+import android.net.Uri;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.ArrayMap;
@@ -31,9 +34,12 @@
 
 import com.android.launcher3.Flags;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.proxy.ProxyActivityStarter;
+import com.android.launcher3.util.StartActivityParams;
 import com.android.launcher3.util.UserIconInfo;
 import com.android.quickstep.util.FadeOutRemoteTransition;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
@@ -102,6 +108,44 @@
         return users;
     }
 
+    /**
+     * Returns the list of the system packages that are installed at user creation.
+     * An empty list denotes that all system packages are installed for that user at creation.
+     */
+    public static List<String> getPreInstalledSystemPackages(Context context, UserHandle user) {
+        LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
+        if (android.os.Flags.allowPrivateProfile() && Flags.enablePrivateSpace()
+                && Flags.privateSpaceSysAppsSeparation()) {
+            return launcherApps.getPreInstalledSystemPackages(user);
+        } else {
+            return new ArrayList<>();
+        }
+    }
+
+    /**
+     * Returns an intent which can be used to start the App Market activity (Installer
+     * Activity).
+     */
+    public static Intent getAppMarketActivityIntent(Context context, String packageName,
+            UserHandle user) {
+        LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
+        if (android.os.Flags.allowPrivateProfile() && Flags.enablePrivateSpace()
+                && Flags.privateSpaceAppInstallerButton()) {
+            StartActivityParams params = new StartActivityParams((PendingIntent) null, 0);
+            params.intentSender = launcherApps.getAppMarketActivityIntent(packageName, user);
+            return ProxyActivityStarter.getLaunchIntent(context, params);
+        } else {
+            return new Intent(Intent.ACTION_VIEW)
+                    .setData(new Uri.Builder()
+                            .scheme("market")
+                            .authority("details")
+                            .appendQueryParameter("id", packageName)
+                            .build())
+                    .putExtra(Intent.EXTRA_REFERRER, new Uri.Builder().scheme("android-app")
+                            .authority(context.getPackageName()).build());
+        }
+    }
+
     private static class NoopDrawable extends ColorDrawable {
         @Override
         public int getIntrinsicHeight() {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepAppWidgetHost.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepAppWidgetHost.java
index 6659fa0..45813ce 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepAppWidgetHost.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepAppWidgetHost.java
@@ -25,6 +25,7 @@
 import androidx.annotation.NonNull;
 
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.util.Executors;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.widget.LauncherWidgetHolder;
 
@@ -55,7 +56,10 @@
 
     @Override
     public void onAppWidgetRemoved(int appWidgetId) {
-        mAppWidgetRemovedCallback.accept(appWidgetId);
+        // Route the call via model thread, in case it comes while a loader-bind is in progress
+        Executors.MODEL_EXECUTOR.execute(
+                () -> Executors.MAIN_EXECUTOR.execute(
+                        () -> mAppWidgetRemovedCallback.accept(appWidgetId)));
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 9438e00..71f4faf 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -84,6 +84,7 @@
 import android.window.BackEvent;
 import android.window.OnBackAnimationCallback;
 import android.window.OnBackInvokedDispatcher;
+import android.window.RemoteTransition;
 import android.window.SplashScreen;
 
 import androidx.annotation.BinderThread;
@@ -266,8 +267,8 @@
         mAppTransitionManager.registerRemoteTransitions();
 
         if (FeatureFlags.enableHomeTransitionListener()) {
-            mHomeTransitionController = new HomeTransitionController(this);
-            mHomeTransitionController.registerHomeTransitionListener();
+            mHomeTransitionController = new HomeTransitionController();
+            mHomeTransitionController.registerHomeTransitionListener(this);
         }
 
         mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
@@ -644,7 +645,7 @@
                 Collections.singletonList(splitSelectSource.itemInfo.getComponentKey()),
                 false /* findExactPairMatch */,
                 foundTasks -> {
-                    @Nullable Task foundTask = foundTasks.get(0);
+                    @Nullable Task foundTask = foundTasks[0];
                     boolean taskWasFound = foundTask != null;
                     splitSelectSource.alreadyRunningTaskId = taskWasFound
                             ? foundTask.key.id
@@ -1271,7 +1272,8 @@
     /**
      * Launches the given {@link GroupTask} in splitscreen.
      */
-    public void launchSplitTasks(@NonNull GroupTask groupTask) {
+    public void launchSplitTasks(
+            @NonNull GroupTask groupTask, @Nullable RemoteTransition remoteTransition) {
         // Top/left and bottom/right tasks respectively.
         Task task1 = groupTask.task1;
         // task2 should never be null when calling this method. Allow a crash to catch invalid calls
@@ -1285,7 +1287,8 @@
                 /* freezeTaskList= */ false,
                 groupTask.mSplitBounds == null
                         ? SNAP_TO_50_50
-                        : groupTask.mSplitBounds.snapPosition);
+                        : groupTask.mSplitBounds.snapPosition,
+                remoteTransition);
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
index f66bc60..0fb2b17 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
@@ -34,10 +34,10 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.util.IntSet;
+import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.widget.LauncherWidgetHolder;
-import com.android.launcher3.widget.custom.CustomWidgetManager;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -67,13 +67,11 @@
 
     private static AppWidgetHost sWidgetHost = null;
 
-    private final SparseArray<AppWidgetHostView> mViews = new SparseArray<>();
-
+    private final UpdateHandler mUpdateHandler = this::onWidgetUpdate;
     private final @Nullable RemoteViews.InteractionHandler mInteractionHandler;
 
     private final @NonNull IntConsumer mAppWidgetRemovedCallback;
 
-    private final ArrayList<ProviderChangedListener> mProviderChangedListeners = new ArrayList<>();
     // Map to all pending updated keyed with appWidgetId;
     private final SparseArray<PendingUpdate> mPendingUpdateMap = new SparseArray<>();
 
@@ -112,16 +110,12 @@
 
     @Override
     protected void updateDeferredView() {
-        super.updateDeferredView();
         int count = mPendingUpdateMap.size();
         for (int i = 0; i < count; i++) {
             int widgetId = mPendingUpdateMap.keyAt(i);
             AppWidgetHostView view = mViews.get(widgetId);
-            if (view == null) {
-                continue;
-            }
             PendingUpdate pendingUpdate = mPendingUpdateMap.valueAt(i);
-            if (pendingUpdate == null) {
+            if (view == null || pendingUpdate == null) {
                 continue;
             }
             if (pendingUpdate.providerInfo != null) {
@@ -172,7 +166,6 @@
     @Override
     public void deleteAppWidgetId(int appWidgetId) {
         super.deleteAppWidgetId(appWidgetId);
-        mViews.remove(appWidgetId);
         sListeners.remove(appWidgetId);
     }
 
@@ -182,7 +175,10 @@
     @Override
     public void destroy() {
         try {
-            MAIN_EXECUTOR.submit(() -> sHolders.remove(this)).get();
+            MAIN_EXECUTOR.submit(() -> {
+                clearViews();
+                sHolders.remove(this);
+            }).get();
         } catch (Exception e) {
             Log.e(TAG, "Failed to remove self from holder list", e);
         }
@@ -195,26 +191,6 @@
     }
 
     /**
-     * Add a listener that is triggered when the providers of the widgets are changed
-     * @param listener The listener that notifies when the providers changed
-     */
-    @Override
-    public void addProviderChangeListener(
-            @NonNull LauncherWidgetHolder.ProviderChangedListener listener) {
-        MAIN_EXECUTOR.execute(() -> mProviderChangedListeners.add(listener));
-    }
-
-    /**
-     * Remove the specified listener from the host
-     * @param listener The listener that is to be removed from the host
-     */
-    @Override
-    public void removeProviderChangeListener(
-            LauncherWidgetHolder.ProviderChangedListener listener) {
-        MAIN_EXECUTOR.execute(() -> mProviderChangedListeners.remove(listener));
-    }
-
-    /**
      * Stop the host from updating the widget views
      */
     @Override
@@ -227,46 +203,41 @@
         setListeningFlag(false);
     }
 
-    /**
-     * Create a view for the specified app widget
-     * @param context The activity context for which the view is created
-     * @param appWidgetId The ID of the widget
-     * @param appWidget The {@link LauncherAppWidgetProviderInfo} of the widget
-     * @return A view for the widget
-     */
+    @Override
+    public SafeCloseable addOnUpdateListener(int appWidgetId,
+            LauncherAppWidgetProviderInfo appWidget, Runnable callback) {
+        UpdateHandler handler = new UpdateHandler() {
+            @Override
+            public <T> void onWidgetUpdate(int widgetId, UpdateKey<T> key, T data) {
+                if (KEY_VIEWS_UPDATE == key) {
+                    callback.run();
+                }
+            }
+        };
+        QuickstepWidgetHolderListener holderListener = getHolderListener(appWidgetId);
+        holderListener.addHolder(handler);
+        return () -> holderListener.mListeningHolders.remove(handler);
+    }
+
     @NonNull
     @Override
-    public LauncherAppWidgetHostView createView(@NonNull Context context, int appWidgetId,
-            @NonNull LauncherAppWidgetProviderInfo appWidget) {
-
-        if (appWidget.isCustomWidget()) {
-            LauncherAppWidgetHostView lahv = new LauncherAppWidgetHostView(context);
-            lahv.setAppWidget(appWidgetId, appWidget);
-            CustomWidgetManager.INSTANCE.get(context).onViewCreated(lahv);
-            return lahv;
-        }
-
-        LauncherAppWidgetHostView widgetView = getPendingView(appWidgetId);
-        if (widgetView != null) {
-            removePendingView(appWidgetId);
-        } else {
-            widgetView = new LauncherAppWidgetHostView(context);
-        }
-        widgetView.setIsWidgetCachingDisabled(true);
+    protected LauncherAppWidgetHostView createViewInternal(
+            int appWidgetId, @NonNull LauncherAppWidgetProviderInfo appWidget) {
+        LauncherAppWidgetHostView widgetView = new LauncherAppWidgetHostView(mContext);
         widgetView.setInteractionHandler(mInteractionHandler);
         widgetView.setAppWidget(appWidgetId, appWidget);
-        mViews.put(appWidgetId, widgetView);
+        widgetView.updateAppWidget(getHolderListener(appWidgetId).addHolder(mUpdateHandler));
+        return widgetView;
+    }
 
+    private static QuickstepWidgetHolderListener getHolderListener(int appWidgetId) {
         QuickstepWidgetHolderListener listener = sListeners.get(appWidgetId);
         if (listener == null) {
             listener = new QuickstepWidgetHolderListener(appWidgetId);
             sWidgetHost.setListener(appWidgetId, listener);
             sListeners.put(appWidgetId, listener);
         }
-        RemoteViews remoteViews = listener.addHolder(this);
-        widgetView.updateAppWidget(remoteViews);
-
-        return widgetView;
+        return listener;
     }
 
     /**
@@ -276,7 +247,7 @@
     public void clearViews() {
         mViews.clear();
         for (int i = sListeners.size() - 1; i >= 0; i--) {
-            sListeners.valueAt(i).mListeningHolders.remove(this);
+            sListeners.valueAt(i).mListeningHolders.remove(mUpdateHandler);
         }
     }
 
@@ -284,7 +255,7 @@
             implements AppWidgetHost.AppWidgetHostListener {
 
         // Static listeners should use a set that is backed by WeakHashMap to avoid memory leak
-        private final Set<QuickstepWidgetHolder> mListeningHolders = Collections.newSetFromMap(
+        private final Set<UpdateHandler> mListeningHolders = Collections.newSetFromMap(
                 new WeakHashMap<>());
 
         private final int mWidgetId;
@@ -297,7 +268,7 @@
 
         @UiThread
         @Nullable
-        public RemoteViews addHolder(@NonNull QuickstepWidgetHolder holder) {
+        public RemoteViews addHolder(@NonNull UpdateHandler holder) {
             mListeningHolders.add(holder);
             return mRemoteViews;
         }
@@ -368,11 +339,15 @@
         }
     }
 
+    private interface UpdateKey<T> extends BiConsumer<AppWidgetHostView, T> { }
+
+    private interface UpdateHandler {
+        <T> void onWidgetUpdate(int widgetId, UpdateKey<T> key, T data);
+    }
+
     private static class PendingUpdate {
         public final IntSet changedViews = new IntSet();
         public AppWidgetProviderInfo providerInfo;
         public RemoteViews remoteViews;
     }
-
-    private interface UpdateKey<T> extends BiConsumer<AppWidgetHostView, T> { }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
index 1d55da3..856b519 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
@@ -62,12 +62,6 @@
     @Override
     public void onBackPressed(Launcher launcher) {
         launcher.getStateManager().goToState(LauncherState.OVERVIEW);
-        RecentsView recentsView = launcher.<RecentsView>getOverviewPanel();
-        if (recentsView != null) {
-            recentsView.resetModalVisuals();
-        } else {
-            super.onBackPressed(launcher);
-        }
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index d94cd89..5db5d17 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -106,7 +106,7 @@
     protected StateAnimationConfig getConfigForStates(
             LauncherState fromState, LauncherState toState) {
         final StateAnimationConfig config = new StateAnimationConfig();
-        config.userControlled = true;
+        config.animProps |= StateAnimationConfig.USER_CONTROLLED;
         if (fromState == NORMAL && toState == ALL_APPS) {
             AllAppsSwipeController.applyNormalToAllAppsAnimConfig(mLauncher, config);
         } else if (fromState == ALL_APPS && toState == NORMAL) {
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 42b18bd..cbf6ad6 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -1572,7 +1572,8 @@
                         mSwipePipToHomeAnimator.getTaskId(),
                         mSwipePipToHomeAnimator.getComponentName(),
                         mSwipePipToHomeAnimator.getDestinationBounds(),
-                        mSwipePipToHomeAnimator.getContentOverlay());
+                        mSwipePipToHomeAnimator.getContentOverlay(),
+                        mSwipePipToHomeAnimator.getAppBounds());
 
                 windowAnim = mSwipePipToHomeAnimators;
             } else {
diff --git a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
index 7c24ba8..877bdf8 100644
--- a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
+++ b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
@@ -23,13 +23,13 @@
 import static com.android.launcher3.BaseActivity.INVISIBLE_ALL;
 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_PENDING_FLAGS;
 import static com.android.launcher3.BaseActivity.PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION;
-import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING;
-import static com.android.launcher3.config.FeatureFlags.enableTaskbarPinning;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
+import android.content.ComponentCallbacks;
+import android.content.res.Configuration;
 import android.graphics.Matrix;
 import android.graphics.PointF;
 import android.graphics.Rect;
@@ -54,12 +54,14 @@
 
 import com.android.internal.view.AppearanceRegion;
 import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.QuickstepTransitionManager;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.taskbar.LauncherTaskbarUIController;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
+import com.android.launcher3.widget.LauncherAppWidgetHostView;
 import com.android.quickstep.util.RectFSpringAnim;
 import com.android.systemui.shared.system.QuickStepContract;
 
@@ -99,43 +101,45 @@
     private final RectF mCurrentRect = new RectF();
     private final QuickstepLauncher mLauncher;
     private final int mWindowScaleMarginX;
-    /** Max window translation in the Y axis. */
-    private final int mWindowMaxDeltaY;
-    private final float mWindowScaleEndCornerRadius;
-    private final float mWindowScaleStartCornerRadius;
+    private float mWindowScaleEndCornerRadius;
+    private float mWindowScaleStartCornerRadius;
     private final Interpolator mCancelInterpolator;
     private final Interpolator mProgressInterpolator = new DecelerateInterpolator();
     private final PointF mInitialTouchPos = new PointF();
 
     private RemoteAnimationTarget mBackTarget;
-    private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+    private View mLauncherTargetView;
+    private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
     private boolean mSpringAnimationInProgress = false;
     private boolean mAnimatorSetInProgress = false;
     private float mBackProgress = 0;
     private boolean mBackInProgress = false;
     private OnBackInvokedCallbackStub mBackCallback;
     private IRemoteAnimationFinishedCallback mAnimationFinishedCallback;
-    private BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
+    private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
     private SurfaceControl mScrimLayer;
     private ValueAnimator mScrimAlphaAnimator;
     private float mScrimAlpha;
     private boolean mOverridingStatusBarFlags;
 
+    private final ComponentCallbacks mComponentCallbacks = new ComponentCallbacks() {
+        @Override
+        public void onConfigurationChanged(Configuration newConfig) {
+            loadCornerRadius();
+        }
+
+        @Override
+        public void onLowMemory() {}
+    };
+
     public LauncherBackAnimationController(
             QuickstepLauncher launcher,
             QuickstepTransitionManager quickstepTransitionManager) {
         mLauncher = launcher;
         mQuickstepTransitionManager = quickstepTransitionManager;
-        mWindowScaleEndCornerRadius = QuickStepContract.supportsRoundedCornersOnWindows(
-                mLauncher.getResources())
-                ? mLauncher.getResources().getDimensionPixelSize(
-                        R.dimen.swipe_back_window_corner_radius)
-                : 0;
-        mWindowScaleStartCornerRadius = QuickStepContract.getWindowCornerRadius(mLauncher);
+        loadCornerRadius();
         mWindowScaleMarginX = mLauncher.getResources().getDimensionPixelSize(
                 R.dimen.swipe_back_window_scale_x_margin);
-        mWindowMaxDeltaY = mLauncher.getResources().getDimensionPixelSize(
-                R.dimen.swipe_back_window_max_delta_y);
         mCancelInterpolator =
                 AnimationUtils.loadInterpolator(mLauncher, R.interpolator.standard_interpolator);
     }
@@ -287,7 +291,7 @@
         mBackInProgress = true;
         RemoteAnimationTarget appTarget = backEvent.getDepartingAnimationTarget();
 
-        if (appTarget == null) {
+        if (appTarget == null || appTarget.leash == null || !appTarget.leash.isValid()) {
             return;
         }
 
@@ -298,16 +302,26 @@
         mInitialTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
 
         mStartRect.set(appTarget.windowConfiguration.getMaxBounds());
-        if (mLauncher.getDeviceProfile().isTaskbarPresent && enableTaskbarPinning()
-                && LauncherPrefs.get(mLauncher).get(TASKBAR_PINNING)) {
-            int insetBottom = mStartRect.bottom - appTarget.contentInsets.bottom;
-            mStartRect.set(mStartRect.left, mStartRect.top, mStartRect.right, insetBottom);
-        }
+
+        // inset bottom in case of pinned taskbar being present
+        mStartRect.inset(0, 0, 0, appTarget.contentInsets.bottom);
+
+        mLauncherTargetView = mQuickstepTransitionManager.findLauncherView(
+                new RemoteAnimationTarget[]{ mBackTarget });
+        setLauncherTargetViewVisible(false);
         mCurrentRect.set(mStartRect);
         addScrimLayer();
         mTransaction.apply();
     }
 
+    private void setLauncherTargetViewVisible(boolean isVisible) {
+        if (mLauncherTargetView instanceof BubbleTextView) {
+            ((BubbleTextView) mLauncherTargetView).setIconVisible(isVisible);
+        } else if (mLauncherTargetView instanceof LauncherAppWidgetHostView) {
+            mLauncherTargetView.setAlpha(isVisible ? 1f : 0f);
+        }
+    }
+
     void addScrimLayer() {
         ViewRootImpl viewRootImpl = mLauncher.getDragLayer().getViewRootImpl();
         SurfaceControl parent = viewRootImpl != null
@@ -436,6 +450,8 @@
             mLauncher.getStateManager().moveToRestState();
         }
 
+        setLauncherTargetViewVisible(true);
+
         // Explicitly close opened floating views (which is typically called from
         // Launcher#onResumed, but in the predictive back flow launcher is not resumed until
         // the transition is fully finished.)
@@ -456,6 +472,7 @@
                     mBackInProgress /* fromPredictiveBack */);
         startTransitionAnimations(pair.first, pair.second);
         mLauncher.clearForceInvisibleFlag(INVISIBLE_ALL);
+        customizeStatusBarAppearance(true);
     }
 
     private void finishAnimation() {
@@ -469,6 +486,8 @@
         mInitialTouchPos.set(0, 0);
         mAnimatorSetInProgress = false;
         mSpringAnimationInProgress = false;
+        setLauncherTargetViewVisible(true);
+        mLauncherTargetView = null;
         // We don't call customizeStatusBarAppearance here to prevent the status bar update with
         // the legacy appearance. It should be refreshed after the transition done.
         mOverridingStatusBarFlags = false;
@@ -532,6 +551,30 @@
         anim.start();
     }
 
+    private void loadCornerRadius() {
+        mWindowScaleEndCornerRadius = QuickStepContract.supportsRoundedCornersOnWindows(
+                mLauncher.getResources())
+                ? mLauncher.getResources().getDimensionPixelSize(
+                R.dimen.swipe_back_window_corner_radius)
+                : 0;
+        mWindowScaleStartCornerRadius = QuickStepContract.getWindowCornerRadius(mLauncher);
+    }
+
+    /**
+     * Called when launcher is destroyed. Unregisters component callbacks to avoid memory leaks.
+     */
+    public void unregisterComponentCallbacks() {
+        mLauncher.unregisterComponentCallbacks(mComponentCallbacks);
+    }
+
+    /**
+     * Registers component callbacks with the launcher to receive configuration change events.
+     */
+    public void registerComponentCallbacks() {
+        mLauncher.registerComponentCallbacks(mComponentCallbacks);
+    }
+
+
     private void resetScrim() {
         removeScrimLayer();
         mScrimAlpha = 0;
diff --git a/quickstep/src/com/android/quickstep/LauncherRestoreEventLoggerImpl.kt b/quickstep/src/com/android/quickstep/LauncherRestoreEventLoggerImpl.kt
index fc450f0..0913fca 100644
--- a/quickstep/src/com/android/quickstep/LauncherRestoreEventLoggerImpl.kt
+++ b/quickstep/src/com/android/quickstep/LauncherRestoreEventLoggerImpl.kt
@@ -5,9 +5,10 @@
 import android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType
 import android.app.backup.BackupRestoreEventLogger.BackupRestoreError
 import android.content.Context
-import com.android.launcher3.Flags
+import com.android.launcher3.Flags.enableLauncherBrMetrics
 import com.android.launcher3.LauncherSettings.Favorites
 import com.android.launcher3.backuprestore.LauncherRestoreEventLogger
+import com.android.launcher3.config.FeatureFlags.ENABLE_LAUNCHER_BR_METRICS
 
 /**
  * Concrete implementation for wrapper to log Restore event metrics for both success and failure to
@@ -44,7 +45,7 @@
         count: Int,
         @BackupRestoreError error: String?
     ) {
-        if (Flags.enableLauncherBrMetrics()) {
+        if (enableLauncherBrMetrics() || ENABLE_LAUNCHER_BR_METRICS.get()) {
             restoreEventLogger.logItemsRestoreFailed(dataType, count, error)
         }
     }
@@ -56,7 +57,7 @@
      * @param count the number of data items restored.
      */
     override fun logLauncherItemsRestored(@BackupRestoreDataType dataType: String, count: Int) {
-        if (Flags.enableLauncherBrMetrics()) {
+        if (enableLauncherBrMetrics() || ENABLE_LAUNCHER_BR_METRICS.get()) {
             restoreEventLogger.logItemsRestored(dataType, count)
         }
     }
@@ -67,7 +68,7 @@
      * @param favoritesId The id of the item type from [Favorites] that was restored.
      */
     override fun logSingleFavoritesItemRestored(favoritesId: Int) {
-        if (Flags.enableLauncherBrMetrics()) {
+        if (enableLauncherBrMetrics() || ENABLE_LAUNCHER_BR_METRICS.get()) {
             restoreEventLogger.logItemsRestored(favoritesIdToDataType(favoritesId), 1)
         }
     }
@@ -79,7 +80,7 @@
      * @param count number of items that restored.
      */
     override fun logFavoritesItemsRestored(favoritesId: Int, count: Int) {
-        if (Flags.enableLauncherBrMetrics()) {
+        if (enableLauncherBrMetrics() || ENABLE_LAUNCHER_BR_METRICS.get()) {
             restoreEventLogger.logItemsRestored(favoritesIdToDataType(favoritesId), count)
         }
     }
@@ -94,7 +95,7 @@
         favoritesId: Int,
         @BackupRestoreError error: String?
     ) {
-        if (Flags.enableLauncherBrMetrics()) {
+        if (enableLauncherBrMetrics() || ENABLE_LAUNCHER_BR_METRICS.get()) {
             restoreEventLogger.logItemsRestoreFailed(favoritesIdToDataType(favoritesId), 1, error)
         }
     }
@@ -111,7 +112,7 @@
         count: Int,
         @BackupRestoreError error: String?
     ) {
-        if (Flags.enableLauncherBrMetrics()) {
+        if (enableLauncherBrMetrics() || ENABLE_LAUNCHER_BR_METRICS.get()) {
             restoreEventLogger.logItemsRestoreFailed(
                 favoritesIdToDataType(favoritesId),
                 count,
@@ -125,7 +126,7 @@
      * done restoring items for Launcher.
      */
     override fun reportLauncherRestoreResults() {
-        if (Flags.enableLauncherBrMetrics()) {
+        if (enableLauncherBrMetrics() || ENABLE_LAUNCHER_BR_METRICS.get()) {
             BackupManager(context).reportDelayedRestoreResult(restoreEventLogger)
         }
     }
diff --git a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 0303791..65c8662 100644
--- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -1,5 +1,6 @@
 package com.android.quickstep;
 
+import static com.android.launcher3.taskbar.TaskbarThresholdUtils.getFromNavThreshold;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
 import android.app.Activity;
@@ -10,7 +11,6 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.R;
 import com.android.launcher3.taskbar.TaskbarActivityContext;
 import com.android.launcher3.testing.TestInformationHandler;
 import com.android.launcher3.testing.shared.TestProtocol;
@@ -18,6 +18,7 @@
 import com.android.launcher3.util.DisplayController;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.util.TISBindHelper;
+import com.android.quickstep.views.RecentsView;
 
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
@@ -77,6 +78,11 @@
                 return response;
             }
 
+            case TestProtocol.REQUEST_GET_OVERVIEW_CURRENT_PAGE_INDEX: {
+                return getLauncherUIProperty(Bundle::putInt,
+                        launcher -> launcher.<RecentsView>getOverviewPanel().getCurrentPage());
+            }
+
             case TestProtocol.REQUEST_HAS_TIS: {
                 response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, true);
                 return response;
@@ -93,7 +99,7 @@
             case TestProtocol.REQUEST_TASKBAR_FROM_NAV_THRESHOLD: {
                 final Resources resources = mContext.getResources();
                 response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
-                        resources.getDimensionPixelSize(R.dimen.taskbar_from_nav_threshold));
+                        getFromNavThreshold(resources, mDeviceProfile));
                 return response;
             }
 
@@ -149,6 +155,19 @@
             case TestProtocol.REQUEST_REFRESH_OVERVIEW_TARGET:
                 runOnTISBinder(TouchInteractionService.TISBinder::refreshOverviewTarget);
                 return response;
+
+            case TestProtocol.REQUEST_RECREATE_TASKBAR:
+                // Allow null-pointer to catch illegal states.
+                runOnTISBinder(tisBinder -> tisBinder.getTaskbarManager().recreateTaskbar());
+                return response;
+
+            case TestProtocol.REQUEST_UNSTASH_BUBBLE_BAR_IF_STASHED:
+                runOnTISBinder(tisBinder -> {
+                    // Allow null-pointer to catch illegal states.
+                    tisBinder.getTaskbarManager().getCurrentActivityContext()
+                            .unstashBubbleBarIfStashed();
+                });
+                return response;
         }
 
         return super.call(method, arg, extras);
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 94ed5b9..a8c6809 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -630,10 +630,11 @@
      * should be responsible for cleaning up the overlay.
      */
     public void stopSwipePipToHome(int taskId, ComponentName componentName, Rect destinationBounds,
-            SurfaceControl overlay) {
+            SurfaceControl overlay, Rect appBounds) {
         if (mPip != null) {
             try {
-                mPip.stopSwipePipToHome(taskId, componentName, destinationBounds, overlay);
+                mPip.stopSwipePipToHome(taskId, componentName, destinationBounds, overlay,
+                        appBounds);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call stopSwipePipToHome");
             }
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 8e03a91..4f885af 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -38,6 +38,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 
+import com.android.internal.util.ArrayUtils;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.DisplayController;
@@ -185,6 +186,16 @@
                 cleanUpRecentsAnimation(newCallbacks);
             }
 
+            private boolean isNonRecentsStartedTasksAppeared(
+                    RemoteAnimationTarget[] appearedTaskTargets) {
+                // For example, right after swiping from task X to task Y (e.g. from
+                // AbsSwipeUpHandler#startNewTask), and then task Y starts X immediately
+                // (e.g. in Y's onResume). The case will be: lastStartedTask=Y and appearedTask=X.
+                return mLastGestureState.getEndTarget() == GestureState.GestureEndTarget.NEW_TASK
+                        && ArrayUtils.find(appearedTaskTargets,
+                                mLastGestureState.mLastStartedTaskIdPredicate) == null;
+            }
+
             @Override
             public void onTasksAppeared(RemoteAnimationTarget[] appearedTaskTargets) {
                 RemoteAnimationTarget appearedTaskTarget = appearedTaskTargets[0];
@@ -206,13 +217,15 @@
                     }
                 }
 
-                RemoteAnimationTarget[] nonAppTargets = SystemUiProxy.INSTANCE.get(mCtx)
-                        .onStartingSplitLegacy(appearedTaskTargets);
+                RemoteAnimationTarget[] nonAppTargets = ENABLE_SHELL_TRANSITIONS
+                        ? null : SystemUiProxy.INSTANCE.get(mCtx).onStartingSplitLegacy(
+                                appearedTaskTargets);
                 if (nonAppTargets == null) {
                     nonAppTargets = new RemoteAnimationTarget[0];
                 }
                 if ((activityInterface.isInLiveTileMode()
-                            || mLastGestureState.getEndTarget() == RECENTS)
+                            || mLastGestureState.getEndTarget() == RECENTS
+                            || isNonRecentsStartedTasksAppeared(appearedTaskTargets))
                         && activityInterface.getCreatedActivity() != null) {
                     RecentsView recentsView =
                             activityInterface.getCreatedActivity().getOverviewPanel();
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index d1d2f97..9972e59 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -86,7 +86,6 @@
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.ConstantItem;
-import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.EncryptionType;
 import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.R;
@@ -306,6 +305,10 @@
         @Override
         public void onNavigationBarSurface(SurfaceControl surface) {
             // TODO: implement
+            if (surface != null) {
+                surface.release();
+                surface = null;
+            }
         }
 
         @BinderThread
@@ -369,6 +372,12 @@
                     taskbarManager.onNavButtonsDarkIntensityChanged(darkIntensity));
         }
 
+        @Override
+        public void onNavigationBarLumaSamplingEnabled(int displayId, boolean enable) {
+            executeForTaskbarManager(taskbarManager ->
+                    taskbarManager.onNavigationBarLumaSamplingEnabled(displayId, enable));
+        }
+
         private void executeForTouchInteractionService(
                 @NonNull Consumer<TouchInteractionService> tisConsumer) {
             TouchInteractionService tis = mTis.get();
@@ -725,8 +734,7 @@
         final int action = event.getActionMasked();
         // Note this will create a new consumer every mouse click, as after ACTION_UP from the click
         // an ACTION_HOVER_ENTER will fire as well.
-        boolean isHoverActionWithoutConsumer =
-                event.isHoverEvent() && (mUncheckedConsumer.getType() & TYPE_CURSOR_HOVER) == 0;
+        boolean isHoverActionWithoutConsumer = isHoverActionWithoutConsumer(event);
         CompoundString reasonString = action == ACTION_DOWN
                 ? new CompoundString("onMotionEvent: ") : CompoundString.NO_OP;
         if (action == ACTION_DOWN || isHoverActionWithoutConsumer) {
@@ -846,6 +854,15 @@
         traceToken.close();
     }
 
+    private boolean isHoverActionWithoutConsumer(MotionEvent event) {
+        // Only process these events when taskbar is present.
+        TaskbarActivityContext tac = mTaskbarManager.getCurrentActivityContext();
+        boolean isTaskbarPresent = tac != null && tac.getDeviceProfile().isTaskbarPresent
+                && !tac.isPhoneMode();
+        return event.isHoverEvent() && (mUncheckedConsumer.getType() & TYPE_CURSOR_HOVER) == 0
+                && isTaskbarPresent;
+    }
+
     // Talkback generates hover events on touch, which we do not want to consume.
     private boolean isCursorHoverEvent(MotionEvent event) {
         return event.isHoverEvent() && event.getSource() == InputDevice.SOURCE_MOUSE;
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 35fa539..1008da3 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -217,7 +217,6 @@
         } else {
             if (mActivity.isInState(RecentsState.MODAL_TASK)) {
                 mActivity.getStateManager().goToState(DEFAULT, animate);
-                resetModalVisuals();
             }
         }
     }
@@ -237,6 +236,8 @@
         setOverviewFullscreenEnabled(toState.isFullScreen());
         if (toState == MODAL_TASK) {
             setOverviewSelectEnabled(true);
+        } else {
+            resetModalVisuals();
         }
 
         // Set border after select mode changes to avoid showing border during state transition
diff --git a/quickstep/src/com/android/quickstep/util/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java
index 3ca2531..0f3c029 100644
--- a/quickstep/src/com/android/quickstep/util/AppPairsController.java
+++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java
@@ -130,7 +130,7 @@
                 Arrays.asList(app1Key, app2Key),
                 false /* findExactPairMatch */,
                 foundTasks -> {
-                    @Nullable Task foundTask1 = foundTasks.get(0);
+                    @Nullable Task foundTask1 = foundTasks[0];
                     Intent task1Intent;
                     int task1Id;
                     if (foundTask1 != null) {
@@ -147,7 +147,7 @@
                             LAUNCHER_APP_PAIR_LAUNCH,
                             task1Id);
 
-                    @Nullable Task foundTask2 = foundTasks.get(1);
+                    @Nullable Task foundTask2 = foundTasks[1];
                     if (foundTask2 != null) {
                         mSplitSelectStateController.setSecondTask(foundTask2);
                     } else {
diff --git a/quickstep/src/com/android/quickstep/util/SlideInRemoteTransition.kt b/quickstep/src/com/android/quickstep/util/SlideInRemoteTransition.kt
new file mode 100644
index 0000000..c1fa2f3
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/SlideInRemoteTransition.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util
+
+import android.animation.ValueAnimator
+import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
+import android.graphics.Rect
+import android.os.IBinder
+import android.os.RemoteException
+import android.view.SurfaceControl
+import android.view.SurfaceControl.Transaction
+import android.window.IRemoteTransition
+import android.window.IRemoteTransitionFinishedCallback
+import android.window.TransitionInfo
+import com.android.launcher3.anim.AnimatorListeners.forEndCallback
+import com.android.launcher3.util.Executors
+import com.android.wm.shell.util.TransitionUtil
+
+/** Remote animation which slides the opening targets in and the closing targets out */
+class SlideInRemoteTransition(val isRtl: Boolean) : IRemoteTransition.Stub() {
+
+    override fun mergeAnimation(
+        iBinder: IBinder,
+        transitionInfo: TransitionInfo,
+        transaction: Transaction,
+        mergeTarget: IBinder,
+        finishCB: IRemoteTransitionFinishedCallback
+    ) {
+
+        try {
+            finishCB.onTransitionFinished(null, Transaction())
+        } catch (e: RemoteException) {
+            // Ignore
+        }
+    }
+
+    override fun startAnimation(
+        transition: IBinder,
+        info: TransitionInfo,
+        startT: Transaction,
+        finishCB: IRemoteTransitionFinishedCallback
+    ) {
+        val anim = ValueAnimator.ofFloat(0f, 1f)
+
+        val closingStartBounds: HashMap<SurfaceControl, Rect> = HashMap()
+        val openingEndBounds: HashMap<SurfaceControl, Rect> = HashMap()
+        for (chg in info.changes) {
+            val leash = chg.leash
+            startT.show(leash)
+
+            val taskInfo = chg.taskInfo
+            if (taskInfo?.activityType == ACTIVITY_TYPE_HOME || taskInfo?.parentTaskId != -1) {
+                continue
+            }
+            if (TransitionUtil.isClosingType(chg.mode)) {
+                closingStartBounds[leash] = chg.startAbsBounds
+            }
+            if (TransitionUtil.isOpeningType(chg.mode)) {
+                openingEndBounds[leash] = chg.endAbsBounds
+            }
+        }
+        startT.apply()
+
+        anim.addUpdateListener {
+            val t = Transaction()
+            closingStartBounds.keys.forEach {
+                // Translate the surface from its original position on-screen to off-screen on the
+                // right (or left in RTL)
+                val startBounds = closingStartBounds[it]
+                val targetX = (if (isRtl) -1 else 1) * startBounds!!.right
+                t.setPosition(it, anim.animatedValue as Float * targetX, 0f)
+            }
+            openingEndBounds.keys.forEach {
+                // Set the alpha in the update listener to prevent one visible frame at the
+                // beginning
+                t.setAlpha(it, 1f)
+                // Translate the surface from off-screen on the left (or left in RTL) to its final
+                // position on-screen
+                val endBounds = openingEndBounds[it]
+                val targetX = (if (isRtl) -1 else 1) * endBounds!!.right
+                t.setPosition(it, (1f - anim.animatedValue as Float) * -targetX, 0f)
+            }
+            t.apply()
+        }
+        anim.addListener(
+            forEndCallback(
+                Runnable {
+                    val t = Transaction()
+                    try {
+                        finishCB.onTransitionFinished(null, t)
+                    } catch (e: RemoteException) {
+                        // Ignore
+                    }
+                }
+            )
+        )
+
+        Executors.MAIN_EXECUTOR.execute { anim.start() }
+    }
+
+    override fun onTransitionConsumed(transition: IBinder?, aborted: Boolean) {}
+}
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index b2d1b43..ad9f5ea 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -49,6 +49,7 @@
 import com.android.launcher3.statehandlers.DepthController
 import com.android.launcher3.statemanager.StateManager
 import com.android.launcher3.statemanager.StatefulActivity
+import com.android.launcher3.taskbar.TaskbarActivityContext
 import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource
 import com.android.launcher3.views.BaseDragLayer
 import com.android.quickstep.TaskViewUtils
@@ -390,6 +391,14 @@
                 "trying to launch an app pair icon, but encountered an unexpected null"
             }
 
+            // If launching an app pair from Taskbar inside of an app context, use fade-in animation
+            // TODO (b/316485863): Replace with desired app pair launch animation
+            if (launchingIconView.context is TaskbarActivityContext) {
+                composeFadeInSplitLaunchAnimator(
+                    initialTaskId, secondTaskId, info, t, finishCallback)
+                return
+            }
+
             composeIconSplitLaunchAnimator(launchingIconView, info, t, finishCallback)
         } else {
             // Fallback case: simple fade-in animation
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index d5899e4..3c90e0c 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -105,7 +105,7 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Collections;
+import java.util.Arrays;
 import java.util.List;
 import java.util.function.Consumer;
 
@@ -225,14 +225,14 @@
      *                           tasks (i.e. searching for a running pair of tasks.)
      */
     public void findLastActiveTasksAndRunCallback(@Nullable List<ComponentKey> componentKeys,
-            boolean findExactPairMatch, Consumer<List<Task>> callback) {
+            boolean findExactPairMatch, Consumer<Task[]> callback) {
         mRecentTasksModel.getTasks(taskGroups -> {
             if (componentKeys == null || componentKeys.isEmpty()) {
-                callback.accept(Collections.emptyList());
+                callback.accept(new Task[]{});
                 return;
             }
 
-            List<Task> lastActiveTasks = new ArrayList<>();
+            Task[] lastActiveTasks = new Task[componentKeys.size()];
 
             if (findExactPairMatch) {
                 // Loop through tasks in reverse, since they are ordered with most-recent tasks last
@@ -240,32 +240,35 @@
                     GroupTask groupTask = taskGroups.get(i);
                     if (isInstanceOfAppPair(
                             groupTask, componentKeys.get(0), componentKeys.get(1))) {
-                        lastActiveTasks.add(groupTask.task1);
+                        lastActiveTasks[0] = groupTask.task1;
                         break;
                     }
                 }
             } else {
                 // For each key we are looking for, add to lastActiveTasks with the corresponding
                 // Task (or do nothing if not found).
-                for (ComponentKey key : componentKeys) {
+                for (int i = 0; i < componentKeys.size(); i++) {
+                    ComponentKey key = componentKeys.get(i);
                     Task lastActiveTask = null;
                     // Loop through tasks in reverse, since they are ordered with recent tasks last
-                    for (int i = taskGroups.size() - 1; i >= 0; i--) {
-                        GroupTask groupTask = taskGroups.get(i);
+                    for (int j = taskGroups.size() - 1; j >= 0; j--) {
+                        GroupTask groupTask = taskGroups.get(j);
                         Task task1 = groupTask.task1;
                         // Don't add duplicate Tasks
-                        if (isInstanceOfComponent(task1, key) && !lastActiveTasks.contains(task1)) {
+                        if (isInstanceOfComponent(task1, key)
+                                && !Arrays.asList(lastActiveTasks).contains(task1)) {
                             lastActiveTask = task1;
                             break;
                         }
                         Task task2 = groupTask.task2;
-                        if (isInstanceOfComponent(task2, key) && !lastActiveTasks.contains(task2)) {
+                        if (isInstanceOfComponent(task2, key)
+                                && !Arrays.asList(lastActiveTasks).contains(task2)) {
                             lastActiveTask = task2;
                             break;
                         }
                     }
 
-                    lastActiveTasks.add(lastActiveTask);
+                    lastActiveTasks[i] = lastActiveTask;
                 }
             }
 
@@ -495,6 +498,29 @@
     }
 
     /**
+     * Used to launch split screen from a split pair that already exists, optionally with a custom
+     * remote transition.
+     * <p>
+     * See {@link SplitSelectStateController#launchExistingSplitPair(
+     * GroupedTaskView, int, int, int, Consumer, boolean, int, RemoteTransition)}
+     */
+    public void launchExistingSplitPair(@Nullable GroupedTaskView groupedTaskView,
+            int firstTaskId, int secondTaskId, @StagePosition int stagePosition,
+            Consumer<Boolean> callback, boolean freezeTaskList,
+            @PersistentSnapPosition int snapPosition) {
+        launchExistingSplitPair(
+                groupedTaskView,
+                firstTaskId,
+                secondTaskId,
+                stagePosition,
+                callback,
+                freezeTaskList,
+                snapPosition,
+                /* remoteTransition= */ null);
+    }
+
+
+    /**
      * Used to launch split screen from a split pair that already exists (usually accessible through
      * Overview). This is different than {@link #launchTasks(Consumer, boolean, int, InstanceId)}
      * in that this only launches split screen that are existing tasks. This doesn't determine which
@@ -506,7 +532,7 @@
     public void launchExistingSplitPair(@Nullable GroupedTaskView groupedTaskView,
             int firstTaskId, int secondTaskId, @StagePosition int stagePosition,
             Consumer<Boolean> callback, boolean freezeTaskList,
-            @PersistentSnapPosition int snapPosition) {
+            @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition) {
         mLaunchingTaskView = groupedTaskView;
         final ActivityOptions options1 = ActivityOptions.makeBasic();
         if (freezeTaskList) {
@@ -515,10 +541,12 @@
         Bundle optionsBundle = options1.toBundle();
 
         if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
-            final RemoteTransition remoteTransition = getShellRemoteTransition(firstTaskId,
-                    secondTaskId, callback, "LaunchExistingPair");
+            final RemoteTransition transition = remoteTransition == null
+                    ? getShellRemoteTransition(
+                            firstTaskId, secondTaskId, callback, "LaunchExistingPair")
+                    : remoteTransition;
             mSystemUiProxy.startTasks(firstTaskId, optionsBundle, secondTaskId, null /* options2 */,
-                    stagePosition, snapPosition, remoteTransition, null /*shellInstanceId*/);
+                    stagePosition, snapPosition, transition, null /*shellInstanceId*/);
         } else {
             final RemoteAnimationAdapter adapter = getLegacyRemoteAdapter(firstTaskId,
                     secondTaskId, callback);
@@ -664,8 +692,10 @@
 
             MAIN_EXECUTOR.execute(() -> {
                 // Only animate from taskView if it's already visible
-                boolean shouldLaunchFromTaskView = mLaunchingTaskView != null &&
-                        mLaunchingTaskView.getRecentsView().isTaskViewVisible(mLaunchingTaskView);
+                boolean shouldLaunchFromTaskView = mLaunchingTaskView != null
+                        && mLaunchingTaskView.getRecentsView() != null
+                        && mLaunchingTaskView.getRecentsView().isTaskViewVisible(
+                        mLaunchingTaskView);
                 mSplitAnimationController.playSplitLaunchAnimation(
                         shouldLaunchFromTaskView ? mLaunchingTaskView : null,
                         mLaunchingIconView,
diff --git a/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java b/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java
index cb32c6c..b663970 100644
--- a/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java
@@ -53,6 +53,7 @@
 
     private final QuickstepLauncher mLauncher;
     private final SplitSelectStateController mController;
+    private final RecentsAnimationDeviceState mDeviceState;
     private final OverviewComponentObserver mOverviewComponentObserver;
 
     private final int mSplitPlaceholderSize;
@@ -62,10 +63,9 @@
             SplitSelectStateController controller) {
         mLauncher = launcher;
         mController = controller;
-        RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState(
-                launcher.getApplicationContext());
+        mDeviceState = new RecentsAnimationDeviceState(launcher.getApplicationContext());
         mOverviewComponentObserver = new OverviewComponentObserver(launcher.getApplicationContext(),
-                deviceState);
+                mDeviceState);
 
         mSplitPlaceholderSize = mLauncher.getResources().getDimensionPixelSize(
                 R.dimen.split_placeholder_size);
@@ -97,6 +97,7 @@
 
     public void onDestroy() {
         mOverviewComponentObserver.onDestroy();
+        mDeviceState.destroy();
     }
 
     private class SplitWithKeyboardShortcutRecentsAnimationListener implements
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
index a36b32c..6c89be1 100644
--- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -264,6 +264,10 @@
         return mDestinationBounds;
     }
 
+    public Rect getAppBounds() {
+        return mAppBounds;
+    }
+
     @Nullable
     public SurfaceControl getContentOverlay() {
         return mPipContentOverlay == null ? null : mPipContentOverlay.getLeash();
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index baaa062..065a9c5 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -74,6 +74,7 @@
     private final boolean mIsRecentsRtl;
 
     private final Rect mTaskRect = new Rect();
+    private final Rect mFullTaskSize = new Rect();
     private final PointF mPivot = new PointF();
     private DeviceProfile mDp;
     @StagePosition
@@ -131,6 +132,47 @@
         mDp = dp;
         mLayoutValid = false;
         mOrientationState.setDeviceProfile(dp);
+        calculateTaskSize();
+    }
+
+    private void calculateTaskSize() {
+        if (mDp == null) {
+            return;
+        }
+
+        if (mIsGridTask) {
+            mSizeStrategy.calculateGridTaskSize(mContext, mDp, mFullTaskSize,
+                    mOrientationState.getOrientationHandler());
+        } else {
+            mSizeStrategy.calculateTaskSize(mContext, mDp, mFullTaskSize,
+                    mOrientationState.getOrientationHandler());
+        }
+
+        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
+            mTaskRect.set(mFullTaskSize);
+            mOrientationState.getOrientationHandler()
+                    .setSplitTaskSwipeRect(mDp, mTaskRect, mSplitBounds, mStagePosition);
+            mTaskRect.offset(mTaskRectTranslationX, mTaskRectTranslationY);
+        } else if (mIsDesktopTask) {
+            // For desktop, tasks can take up only part of the screen size.
+            // Full task size represents the whole screen size, but scaled down to fit in recents.
+            // Task rect will represent the scaled down thumbnail position and is placed inside
+            // full task size as it is on the home screen.
+            PointF fullscreenTaskDimension = new PointF();
+            BaseActivityInterface.getTaskDimension(mContext, mDp, fullscreenTaskDimension);
+            // Calculate the scale down factor used in recents
+            float scale = mFullTaskSize.width() / fullscreenTaskDimension.x;
+            mTaskRect.set(mThumbnailPosition);
+            mTaskRect.scale(scale);
+            // Ensure the task rect is inside the full task rect
+            mTaskRect.offset(mFullTaskSize.left, mFullTaskSize.top);
+        } else {
+            mTaskRect.set(mFullTaskSize);
+            mTaskRect.offset(mTaskRectTranslationX, mTaskRectTranslationY);
+        }
     }
 
     /**
@@ -148,44 +190,11 @@
         if (mDp == null) {
             return 1;
         }
-
-        if (mIsGridTask) {
-            mSizeStrategy.calculateGridTaskSize(mContext, mDp, mTaskRect,
-                    mOrientationState.getOrientationHandler());
-        } else {
-            mSizeStrategy.calculateTaskSize(mContext, mDp, mTaskRect,
-                    mOrientationState.getOrientationHandler());
-        }
-
-        Rect fullTaskSize;
-        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, mSplitBounds, mStagePosition);
-            mTaskRect.offset(mTaskRectTranslationX, mTaskRectTranslationY);
-        } else if (mIsDesktopTask) {
-            // For desktop, tasks can take up only part of the screen size.
-            // Full task size represents the whole screen size, but scaled down to fit in recents.
-            // Task rect will represent the scaled down thumbnail position and is placed inside
-            // full task size as it is on the home screen.
-            fullTaskSize = new Rect(mTaskRect);
-            PointF fullscreenTaskDimension = new PointF();
-            BaseActivityInterface.getTaskDimension(mContext, mDp, fullscreenTaskDimension);
-            // Calculate the scale down factor used in recents
-            float scale = fullTaskSize.width() / fullscreenTaskDimension.x;
-            mTaskRect.set(mThumbnailPosition);
-            mTaskRect.scale(scale);
-            // Ensure the task rect is inside the full task rect
-            mTaskRect.offset(fullTaskSize.left, fullTaskSize.top);
-        } else {
-            fullTaskSize = new Rect(mTaskRect);
-            mTaskRect.offset(mTaskRectTranslationX, mTaskRectTranslationY);
-        }
-        fullTaskSize.offset(mTaskRectTranslationX + mPivotOffsetX, mTaskRectTranslationY);
-        return mOrientationState.getFullScreenScaleAndPivot(fullTaskSize, mDp, mPivot);
+        // Copy mFullTaskSize instead of updating it directly so it could be reused next time
+        // without recalculating
+        Rect scaleRect = new Rect(mFullTaskSize);
+        scaleRect.offset(mTaskRectTranslationX + mPivotOffsetX, mTaskRectTranslationY);
+        return mOrientationState.getFullScreenScaleAndPivot(scaleRect, mDp, mPivot);
     }
 
     /**
@@ -209,13 +218,13 @@
         mSplitBounds = splitInfo;
         if (mSplitBounds == null) {
             mStagePosition = STAGE_POSITION_UNDEFINED;
-            return;
+        } else {
+            mStagePosition = mThumbnailPosition.equals(splitInfo.leftTopBounds)
+                    ? STAGE_POSITION_TOP_OR_LEFT : STAGE_POSITION_BOTTOM_OR_RIGHT;
+            mPositionHelper.setSplitBounds(convertLauncherSplitBoundsToShell(mSplitBounds),
+                    mStagePosition);
         }
-        mStagePosition = mThumbnailPosition.equals(splitInfo.leftTopBounds) ?
-                STAGE_POSITION_TOP_OR_LEFT :
-                STAGE_POSITION_BOTTOM_OR_RIGHT;
-        mPositionHelper.setSplitBounds(convertLauncherSplitBoundsToShell(mSplitBounds),
-                mStagePosition);
+        calculateTaskSize();
     }
 
     /**
@@ -261,6 +270,8 @@
     public void setTaskRectTranslation(int taskRectTranslationX, int taskRectTranslationY) {
         mTaskRectTranslationX = taskRectTranslationX;
         mTaskRectTranslationY = taskRectTranslationY;
+        // Re-calculate task size after changing translation
+        calculateTaskSize();
     }
 
     /**
@@ -269,7 +280,7 @@
     public void addAppToOverviewAnim(PendingAnimation pa, TimeInterpolator interpolator) {
         pa.addFloat(fullScreenProgress, AnimatedFloat.VALUE, 1, 0, interpolator);
         if (enableGridOnlyOverview() && mDp.isTablet) {
-            int translationXToMiddle = mDp.widthPx / 2 - mTaskRect.centerX();
+            int translationXToMiddle = mDp.widthPx / 2 - mFullTaskSize.centerX();
             taskPrimaryTranslation.value = translationXToMiddle;
             mPivotOffsetX = translationXToMiddle;
         }
@@ -324,8 +335,8 @@
     public void applyWindowToHomeRotation(Matrix matrix) {
         matrix.postTranslate(mDp.windowX, mDp.windowY);
         postDisplayRotation(deltaRotation(
-                mOrientationState.getRecentsActivityRotation(),
-                mOrientationState.getDisplayRotation()),
+                        mOrientationState.getRecentsActivityRotation(),
+                        mOrientationState.getDisplayRotation()),
                 mDp.widthPx, mDp.heightPx, matrix);
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
index a10d2ed..6b00473 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
@@ -17,6 +17,7 @@
 package com.android.quickstep.views;
 
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
 
 import android.content.Context;
@@ -53,6 +54,8 @@
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.wm.shell.Flags;
 
+import kotlin.Unit;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -60,7 +63,6 @@
 import java.util.List;
 import java.util.function.Consumer;
 
-import kotlin.Unit;
 
 /**
  * TaskView that contains all tasks that are part of the desktop.
@@ -91,6 +93,8 @@
 
     private View mBackgroundView;
 
+    private int mChildCountAtInflation;
+
     /** Check whether desktop windowing is enabled */
     public static boolean isDesktopModeSupported() {
         // Check for aconfig flag first
@@ -151,6 +155,8 @@
         Drawable iconBackground = getResources().getDrawable(R.drawable.bg_circle,
                 getContext().getTheme());
         mIconView.setDrawable(new LayerDrawable(new Drawable[]{iconBackground, icon}));
+
+        mChildCountAtInflation = getChildCount();
     }
 
     @Override
@@ -196,7 +202,9 @@
             for (int i = 0; i < diff; i++) {
                 TaskThumbnailView snapshotView = new TaskThumbnailView(getContext());
                 mSnapshotViews.add(snapshotView);
-                addView(snapshotView, new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+                // Add snapshots from to position after the initial child views.
+                addView(snapshotView, mChildCountAtInflation,
+                        new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
             }
         }
 
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 5b5d0de..9bb9775a 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -16,6 +16,7 @@
 package com.android.quickstep.views;
 
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.CLEAR_ALL_BUTTON;
 import static com.android.launcher3.LauncherState.EDIT_MODE;
@@ -144,6 +145,8 @@
         setOverviewFullscreenEnabled(toState.getOverviewFullscreenProgress() == 1);
         if (toState == OVERVIEW_MODAL_TASK) {
             setOverviewSelectEnabled(true);
+        } else {
+            resetModalVisuals();
         }
 
         // Set border after select mode changes to avoid showing border during state transition
@@ -214,7 +217,6 @@
         } else {
             if (mActivity.isInState(LauncherState.OVERVIEW_MODAL_TASK)) {
                 mActivity.getStateManager().goToState(LauncherState.OVERVIEW, animate);
-                resetModalVisuals();
             }
         }
     }
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index ca9d13e..997624f 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -3246,8 +3246,9 @@
         anim.add(ObjectAnimator.ofFloat(taskView, secondaryViewTranslate,
                 verticalFactor * secondaryTaskDimension * 2).setDuration(duration), LINEAR, sp);
 
-        if (mEnableDrawingLiveTile && taskView.isRunningTask()) {
+        if (taskView.isRunningTask()) {
             anim.addOnFrameCallback(() -> {
+                if (!mEnableDrawingLiveTile) return;
                 runActionOnRemoteHandles(
                         remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
                                 .taskSecondaryTranslation.value = mOrientationHandler
@@ -4381,11 +4382,14 @@
 
     private void updatePivots() {
         if (mOverviewSelectEnabled) {
-            getModalTaskSize(mTempRect);
-            Rect selectedTaskPosition = getSelectedTaskBounds();
-
-            Utilities.getPivotsForScalingRectToRect(mTempRect, selectedTaskPosition,
-                    mTempPointF);
+            if (enableGridOnlyOverview()) {
+                getModalTaskSize(mTempRect);
+                Rect selectedTaskPosition = getSelectedTaskBounds();
+                Utilities.getPivotsForScalingRectToRect(mTempRect, selectedTaskPosition,
+                        mTempPointF);
+            } else {
+                mTempPointF.set(mLastComputedTaskSize.centerX(), mLastComputedTaskSize.bottom);
+            }
         } else {
             mTempRect.set(mLastComputedTaskSize);
             // Only update pivot when it is tablet and not in grid yet, so the pivot is correct
@@ -5708,11 +5712,20 @@
     }
 
     private void updateEnabledOverlays() {
+        TaskView focusedTaskView = getFocusedTaskView();
         int taskCount = getTaskViewCount();
         for (int i = 0; i < taskCount; i++) {
             TaskView taskView = requireTaskViewAt(i);
+            if (taskView == focusedTaskView) {
+                continue;
+            }
             taskView.setOverlayEnabled(mOverlayEnabled && isTaskViewFullyVisible(taskView));
         }
+        // Focus task overlay should be enabled and refreshed at last
+        if (focusedTaskView != null) {
+            focusedTaskView.setOverlayEnabled(
+                    mOverlayEnabled && isTaskViewFullyVisible(focusedTaskView));
+        }
     }
 
     public void setOverlayEnabled(boolean overlayEnabled) {
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index cf89d2e..77033b2 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -115,9 +115,6 @@
             animateClose();
         } else {
             closeComplete();
-            if (enableOverviewIconMenu()) {
-                ((IconAppChipView) mTaskContainer.getIconView()).reset();
-            }
         }
     }
 
@@ -378,9 +375,18 @@
 
     private void closeComplete() {
         mIsOpen = false;
+        resetOverviewIconMenu();
         mActivity.getDragLayer().removeView(this);
     }
 
+    private void resetOverviewIconMenu() {
+        if (enableOverviewIconMenu()) {
+            ((IconAppChipView) mTaskContainer.getIconView()).reset();
+            setTranslationY(mMenuTranslationYBeforeOpen);
+            mTaskContainer.getIconView().asView().setTranslationY(mIconViewTranslationYBeforeOpen);
+        }
+    }
+
     private RoundedRectRevealOutlineProvider createOpenCloseOutlineProvider() {
         float radius = TaskCornerRadius.get(mContext);
         Rect fromRect = new Rect(
diff --git a/quickstep/tests/src/com/android/launcher3/model/AppEventProducerTest.java b/quickstep/tests/src/com/android/launcher3/model/AppEventProducerTest.java
new file mode 100644
index 0000000..d6c1447
--- /dev/null
+++ b/quickstep/tests/src/com/android/launcher3/model/AppEventProducerTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.model;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APPS;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import android.app.prediction.AppTarget;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Process;
+import android.os.UserHandle;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.launcher3.logger.LauncherAtom;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.util.ActivityContextWrapper;
+import com.android.launcher3.util.UserIconInfo;
+import com.android.launcher3.util.rule.StaticMockitoRule;
+import com.android.systemui.shared.system.SysUiStatsLog;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AppEventProducerTest {
+
+    private static final UserHandle MAIN_HANDLE = Process.myUserHandle();
+    private static final UserHandle PRIVATE_HANDLE = new UserHandle(11);
+
+    private static final UserIconInfo MAIN_ICON_INFO =
+            new UserIconInfo(MAIN_HANDLE, UserIconInfo.TYPE_MAIN);
+    private static final UserIconInfo PRIVATE_ICON_INFO =
+            new UserIconInfo(PRIVATE_HANDLE, UserIconInfo.TYPE_PRIVATE);
+
+    private Context mContext;
+    private AppEventProducer mAppEventProducer;
+    @Mock
+    private UserCache mUserCache;
+
+    @Rule
+    public final StaticMockitoRule mStaticMockitoRule = new StaticMockitoRule(UserCache.class);
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = new ActivityContextWrapper(getApplicationContext());
+        when(UserCache.getInstance(any(Context.class))).thenReturn(mUserCache);
+        mAppEventProducer = new AppEventProducer(mContext, null);
+    }
+
+    @Test
+    public void buildAppTarget_containsCorrectUser() {
+        when(mUserCache.getUserProfiles())
+                .thenReturn(Arrays.asList(MAIN_HANDLE, PRIVATE_HANDLE));
+        when(mUserCache.getUserInfo(any(UserHandle.class)))
+                .thenReturn(MAIN_ICON_INFO, PRIVATE_ICON_INFO);
+        ComponentName gmailComponentName = new ComponentName(mContext,
+                "com.android.launcher3.tests.Activity" + "Gmail");
+        AppInfo gmailAppInfo = new
+                AppInfo(gmailComponentName, "Gmail", MAIN_HANDLE, new Intent());
+        gmailAppInfo.container = CONTAINER_ALL_APPS;
+        gmailAppInfo.itemType = ITEM_TYPE_APPLICATION;
+
+        AppTarget gmailTarget = mAppEventProducer
+                .toAppTarget(buildItemInfoProtoForAppInfo(gmailAppInfo));
+
+        assert gmailTarget != null;
+        assertEquals(gmailTarget.getUser(), MAIN_HANDLE);
+
+        when(mUserCache.getUserInfo(any(UserHandle.class)))
+                .thenReturn(MAIN_ICON_INFO, PRIVATE_ICON_INFO);
+        AppInfo gmailAppInfoPrivate = new
+                AppInfo(gmailComponentName, "Gmail", PRIVATE_HANDLE, new Intent());
+        gmailAppInfoPrivate.container = CONTAINER_ALL_APPS;
+        gmailAppInfoPrivate.itemType = ITEM_TYPE_APPLICATION;
+
+        AppTarget gmailPrivateTarget = mAppEventProducer
+                .toAppTarget(buildItemInfoProtoForAppInfo(gmailAppInfoPrivate));
+
+        assert gmailPrivateTarget != null;
+        assertEquals(gmailPrivateTarget.getUser(), PRIVATE_HANDLE);
+    }
+
+    private LauncherAtom.ItemInfo buildItemInfoProtoForAppInfo(AppInfo appInfo) {
+        LauncherAtom.ItemInfo.Builder itemBuilder = LauncherAtom.ItemInfo.newBuilder();
+        if (appInfo.user.equals(PRIVATE_HANDLE)) {
+            itemBuilder.setUserType(SysUiStatsLog.LAUNCHER_UICHANGED__USER_TYPE__TYPE_PRIVATE);
+        } else {
+            itemBuilder.setUserType(SysUiStatsLog.LAUNCHER_UICHANGED__USER_TYPE__TYPE_MAIN);
+        }
+        itemBuilder.setApplication(LauncherAtom.Application.newBuilder()
+                .setComponentName(appInfo.componentName.flattenToShortString())
+                .setPackageName(appInfo.componentName.getPackageName()));
+        itemBuilder.setContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
+                .setAllAppsContainer(LauncherAtom.AllAppsContainer.getDefaultInstance())
+                .build());
+        return itemBuilder.build();
+    }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
index 3a5fb04..9ad360f 100644
--- a/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
+++ b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
@@ -16,8 +16,6 @@
 
 package com.android.quickstep;
 
-import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
-
 import static org.junit.Assert.assertTrue;
 
 import android.os.SystemProperties;
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index e0ee28d..39d6f03 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -28,10 +28,12 @@
 import static com.android.launcher3.ui.AbstractLauncherUiTest.resolveSystemApp;
 import static com.android.launcher3.ui.AbstractLauncherUiTest.startAppFast;
 import static com.android.launcher3.ui.AbstractLauncherUiTest.startTestActivity;
-import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
+import static com.android.launcher3.ui.TaplTestsLauncher3Test.getAppPackageName;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.rule.ShellCommandRule.disableHeadsUpNotification;
 import static com.android.launcher3.util.rule.ShellCommandRule.getLauncherCommand;
+import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
+import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -67,7 +69,6 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.RuleChain;
@@ -76,6 +77,7 @@
 import org.junit.runners.model.Statement;
 
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
 import java.util.function.Function;
@@ -185,12 +187,14 @@
         mLauncher.getLaunchedAppState().switchToOverview();
     }
 
-    // b/143488140
+    // Staging; will be promoted to presubmit if stable
+    @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT)
+
     //@NavigationModeSwitch
-    @Ignore
     @Test
     public void goToOverviewFromApp() {
         startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
+        waitForRecentsActivityStop();
 
         mLauncher.getLaunchedAppState().switchToOverview();
     }
@@ -223,6 +227,23 @@
 
     private void pressHomeAndWaitForOverviewClose() {
         mDevice.pressHome();
+        waitForRecentsActivityStop();
+    }
+
+    private void waitForRecentsActivityStop() {
+        try {
+            final boolean recentsActivityIsNull = MAIN_EXECUTOR.submit(
+                    () -> RecentsActivity.ACTIVITY_TRACKER.getCreatedActivity() == null).get();
+            if (recentsActivityIsNull) {
+                // Null activity counts as a "stopped" one.
+                return;
+            }
+        } catch (ExecutionException e) {
+            throw new RuntimeException(e);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+
         Wait.atMost("Recents activity didn't stop",
                 () -> getFromRecents(recents -> !recents.isStarted()),
                 DEFAULT_UI_TIMEOUT, mLauncher);
@@ -235,6 +256,7 @@
         startAppFast(getAppPackageName());
         startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
         startTestActivity(2);
+        waitForRecentsActivityStop();
         Wait.atMost("Expected three apps in the task list",
                 () -> mLauncher.getRecentTasks().size() >= 3, DEFAULT_ACTIVITY_TIMEOUT, mLauncher);
 
diff --git a/quickstep/tests/src/com/android/quickstep/HotseatWidthCalculationTest.kt b/quickstep/tests/src/com/android/quickstep/HotseatWidthCalculationTest.kt
index a347156..b1ba4c6 100644
--- a/quickstep/tests/src/com/android/quickstep/HotseatWidthCalculationTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/HotseatWidthCalculationTest.kt
@@ -47,7 +47,7 @@
         assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(580)
 
         assertThat(dp.isQsbInline).isFalse()
-        assertThat(dp.hotseatQsbWidth).isEqualTo(1445)
+        assertThat(dp.hotseatQsbWidth).isEqualTo(1435)
     }
 
     /**
@@ -69,7 +69,7 @@
         assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(550)
 
         assertThat(dp.isQsbInline).isFalse()
-        assertThat(dp.hotseatQsbWidth).isEqualTo(1080)
+        assertThat(dp.hotseatQsbWidth).isEqualTo(1070)
     }
 
     /**
@@ -90,7 +90,7 @@
         assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(759)
 
         assertThat(dp.isQsbInline).isFalse()
-        assertThat(dp.hotseatQsbWidth).isEqualTo(1468)
+        assertThat(dp.hotseatQsbWidth).isEqualTo(1455)
     }
 
     /**
@@ -115,7 +115,7 @@
         assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(1040)
 
         assertThat(dp.isQsbInline).isFalse()
-        assertThat(dp.hotseatQsbWidth).isEqualTo(1233)
+        assertThat(dp.hotseatQsbWidth).isEqualTo(1223)
     }
 
     /** This is a case when after setting the hotseat, the QSB width needs to be changed to fit */
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java b/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java
index 829e54b..7191f70 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java
@@ -33,7 +33,16 @@
 public class TaplTestsKeyboardQuickSwitch extends AbstractQuickStepTest {
 
     private enum TestSurface {
-        HOME, LAUNCHED_APP, HOME_ALL_APPS, WIDGETS,
+        HOME(true),
+        LAUNCHED_APP(false),
+        HOME_ALL_APPS(true),
+        WIDGETS(true);
+
+        private final boolean mInitialFocusAtZero;
+
+        TestSurface(boolean initialFocusAtZero) {
+            mInitialFocusAtZero = initialFocusAtZero;
+        }
     }
 
     private enum TestCase {
@@ -172,13 +181,22 @@
                 kqs.dismiss();
                 break;
             case LAUNCH_LAST_APP:
-                kqs.launchFocusedAppTask(CALCULATOR_APP_PACKAGE);
+                kqs.launchFocusedAppTask(testSurface.mInitialFocusAtZero
+                        ? getAppPackageName() : CALCULATOR_APP_PACKAGE);
                 break;
             case LAUNCH_SELECTED_APP:
-                kqs.moveFocusForward().launchFocusedAppTask(CALCULATOR_APP_PACKAGE);
+                kqs.moveFocusForward();
+                if (testSurface.mInitialFocusAtZero) {
+                    kqs.moveFocusForward();
+                }
+                kqs.launchFocusedAppTask(CALCULATOR_APP_PACKAGE);
                 break;
             case LAUNCH_OVERVIEW:
-                kqs.moveFocusBackward().moveFocusBackward().launchFocusedOverviewTask();
+                kqs.moveFocusBackward();
+                if (!testSurface.mInitialFocusAtZero) {
+                    kqs.moveFocusBackward();
+                }
+                kqs.launchFocusedOverviewTask();
                 break;
             default:
                 throw new IllegalStateException("Cannot run test case: " + testCase);
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 25adb62..1b8866a 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -28,7 +28,6 @@
 
 import android.content.Intent;
 import android.content.res.Configuration;
-import android.platform.test.annotations.PlatinumTest;
 
 import androidx.test.filters.LargeTest;
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -44,6 +43,7 @@
 import com.android.launcher3.tapl.Overview;
 import com.android.launcher3.tapl.OverviewActions;
 import com.android.launcher3.tapl.OverviewTask;
+import com.android.launcher3.tapl.SelectModeButtons;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
 import com.android.launcher3.util.Wait;
@@ -112,7 +112,6 @@
 
     @Test
     @PortraitLandscape
-    @PlatinumTest(focusArea = "launcher")
     public void testOverview() throws Exception {
         startTestAppsWithCheck();
         // mLauncher.pressHome() also tests an important case of pressing home while in background.
@@ -194,6 +193,43 @@
         actionsView.clickAndDismissScreenshot();
     }
 
+    @Test
+    public void testDismissOverviewWithEscKey() throws Exception {
+        startTestAppsWithCheck();
+        final Overview overview = mLauncher.goHome().switchToOverview();
+        assertTrue("Launcher internal state is not Overview",
+                isInState(() -> LauncherState.OVERVIEW));
+
+        overview.dismissByEscKey();
+        assertTrue("Launcher internal state is not Home",
+                isInState(() -> LauncherState.NORMAL));
+    }
+
+    @Test
+    public void testDismissModalTaskAndOverviewWithEscKey() throws Exception {
+        startTestAppsWithCheck();
+        final Overview overview = mLauncher.goHome().switchToOverview();
+
+        final SelectModeButtons selectModeButtons;
+
+        if (mLauncher.isTablet() && mLauncher.isGridOnlyOverviewEnabled()) {
+            selectModeButtons = overview.getCurrentTask().tapMenu().tapSelectMenuItem();
+        } else {
+            selectModeButtons = overview.getOverviewActions().clickSelect();
+        }
+
+        assertTrue("Launcher internal state is not Overview Modal Task",
+                isInState(() -> LauncherState.OVERVIEW_MODAL_TASK));
+
+        selectModeButtons.dismissByEscKey();
+
+        assertTrue("Launcher internal state is not Overview",
+                isInState(() -> LauncherState.OVERVIEW));
+        overview.dismissByEscKey();
+        assertTrue("Launcher internal state is not Home",
+                isInState(() -> LauncherState.NORMAL));
+    }
+
     private int getCurrentOverviewPage(Launcher launcher) {
         return launcher.<RecentsView>getOverviewPanel().getCurrentPage();
     }
@@ -210,11 +246,12 @@
         return launcher.<RecentsView>getOverviewPanel().getBottomRowTaskCountForTablet();
     }
 
-    @Ignore
+    // Staging; will be promoted to presubmit if stable
+    @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT)
+
     @Test
     @NavigationModeSwitch
     @PortraitLandscape
-    @ScreenRecord // b/238461765
     public void testSwitchToOverview() throws Exception {
         startTestAppsWithCheck();
         assertNotNull("Workspace.switchToOverview() returned null",
@@ -236,7 +273,9 @@
         }
     }
 
-    @Ignore
+    // Staging; will be promoted to presubmit if stable
+    @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT)
+
     @Test
     @NavigationModeSwitch
     @PortraitLandscape
@@ -295,7 +334,9 @@
 
     @Test
     @TaskbarModeSwitch
-    @Ignore // b/314873201
+    @ScreenRecord // b/314873201
+    // Staging; will be promoted to presubmit if stable
+    @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT)
     public void testQuickSwitchToPreviousAppForTablet() throws Exception {
         assumeTrue(mLauncher.isTablet());
         startTestActivity(2);
@@ -369,7 +410,6 @@
     @Test
     @PortraitLandscape
     @TaskbarModeSwitch()
-    @PlatinumTest(focusArea = "launcher")
     @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/309820115
     @ScreenRecord // b/309820115
     public void testOverviewForTablet() throws Exception {
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsTransientTaskbar.java b/quickstep/tests/src/com/android/quickstep/TaplTestsTransientTaskbar.java
index 7109bbf..38d6046 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsTransientTaskbar.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsTransientTaskbar.java
@@ -25,6 +25,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
+import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
 import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch;
 
 import org.junit.Test;
@@ -69,6 +70,7 @@
     @Test
     @TaskbarModeSwitch(mode = TRANSIENT)
     @PortraitLandscape
+    @ScreenRecord // b/317798731
     public void testSwipeToStashAndUnstash() {
         getTaskbar().swipeDownToStash();
         mLauncher.getLaunchedAppState().swipeUpToUnstashTaskbar();
diff --git a/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt b/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
index 929bd8e..de152fa 100644
--- a/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
@@ -19,6 +19,7 @@
 
 import android.graphics.Bitmap
 import android.graphics.drawable.Drawable
+import android.view.ContextThemeWrapper
 import android.view.SurfaceControl.Transaction
 import android.view.View
 import android.window.TransitionInfo
@@ -26,6 +27,7 @@
 import com.android.launcher3.apppairs.AppPairIcon
 import com.android.launcher3.statehandlers.DepthController
 import com.android.launcher3.statemanager.StateManager
+import com.android.launcher3.taskbar.TaskbarActivityContext
 import com.android.launcher3.util.SplitConfigurationOptions
 import com.android.quickstep.views.GroupedTaskView
 import com.android.quickstep.views.IconView
@@ -64,6 +66,8 @@
     private val mockTaskIdAttributeContainer: TaskIdAttributeContainer = mock()
     // AppPairIcon
     private val mockAppPairIcon: AppPairIcon = mock()
+    private val mockContextThemeWrapper: ContextThemeWrapper = mock()
+    private val mockTaskbarActivityContext: TaskbarActivityContext = mock()
 
     // SplitSelectSource
     private val splitSelectSource: SplitConfigurationOptions.SplitSelectSource = mock()
@@ -247,6 +251,7 @@
     @Test
     fun playsAppropriateSplitLaunchAnimation_playsIconLaunchCorrectly() {
         val spySplitAnimationController = spy(splitAnimationController)
+        whenever(mockAppPairIcon.context).thenReturn(mockContextThemeWrapper)
         doNothing()
             .whenever(spySplitAnimationController)
             .composeIconSplitLaunchAnimator(any(), any(), any(), any())
@@ -271,6 +276,33 @@
     }
 
     @Test
+    fun playsAppropriateSplitLaunchAnimation_playsIconLaunchFromTaskbarContextCorrectly() {
+        val spySplitAnimationController = spy(splitAnimationController)
+        whenever(mockAppPairIcon.context).thenReturn(mockTaskbarActivityContext)
+        doNothing()
+            .whenever(spySplitAnimationController)
+            .composeFadeInSplitLaunchAnimator(any(), any(), any(), any(), any())
+
+        spySplitAnimationController.playSplitLaunchAnimation(
+            null /* launchingTaskView */,
+            mockAppPairIcon,
+            taskId,
+            taskId2,
+            null /* apps */,
+            null /* wallpapers */,
+            null /* nonApps */,
+            stateManager,
+            depthController,
+            transitionInfo,
+            transaction,
+            {} /* finishCallback */
+        )
+
+        verify(spySplitAnimationController)
+            .composeFadeInSplitLaunchAnimator(any(), any(), any(), any(), any())
+    }
+
+    @Test
     fun playsAppropriateSplitLaunchAnimation_playsFadeInLaunchCorrectly() {
         val spySplitAnimationController = spy(splitAnimationController)
         doNothing()
diff --git a/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt b/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
index f41ac48..1e39a34 100644
--- a/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
@@ -107,7 +107,7 @@
         // Assertions happen in the callback we get from what we pass into
         // #findLastActiveTasksAndRunCallback
         val taskConsumer =
-            Consumer<List<Task>> { assertNull("No tasks should have matched", it[0] /*task*/) }
+            Consumer<Array<Task>> { assertNull("No tasks should have matched", it[0] /*task*/) }
 
         // Capture callback from recentsModel#getTasks()
         val consumer =
@@ -148,7 +148,7 @@
         // Assertions happen in the callback we get from what we pass into
         // #findLastActiveTasksAndRunCallback
         val taskConsumer =
-            Consumer<List<Task>> {
+            Consumer<Array<Task>> {
                 assertEquals(
                     "ComponentName package mismatched",
                     it[0].key.baseIntent.component?.packageName,
@@ -201,7 +201,7 @@
         // Assertions happen in the callback we get from what we pass into
         // #findLastActiveTasksAndRunCallback
         val taskConsumer =
-            Consumer<List<Task>> { assertNull("No tasks should have matched", it[0] /*task*/) }
+            Consumer<Array<Task>> { assertNull("No tasks should have matched", it[0] /*task*/) }
 
         // Capture callback from recentsModel#getTasks()
         val consumer =
@@ -244,7 +244,7 @@
         // Assertions happen in the callback we get from what we pass into
         // #findLastActiveTasksAndRunCallback
         val taskConsumer =
-            Consumer<List<Task>> {
+            Consumer<Array<Task>> {
                 assertEquals(
                     "ComponentName package mismatched",
                     it[0].key.baseIntent.component?.packageName,
@@ -298,7 +298,7 @@
         // Assertions happen in the callback we get from what we pass into
         // #findLastActiveTasksAndRunCallback
         val taskConsumer =
-            Consumer<List<Task>> {
+            Consumer<Array<Task>> {
                 assertEquals(
                     "ComponentName package mismatched",
                     it[0].key.baseIntent.component?.packageName,
@@ -350,7 +350,7 @@
         // Assertions happen in the callback we get from what we pass into
         // #findLastActiveTasksAndRunCallback
         val taskConsumer =
-            Consumer<List<Task>> {
+            Consumer<Array<Task>> {
                 assertEquals("Expected array length 2", 2, it.size)
                 assertNull("No tasks should have matched", it[0] /*task*/)
                 assertEquals(
@@ -403,7 +403,7 @@
         // Assertions happen in the callback we get from what we pass into
         // #findLastActiveTasksAndRunCallback
         val taskConsumer =
-            Consumer<List<Task>> {
+            Consumer<Array<Task>> {
                 assertEquals("Expected array length 2", 2, it.size)
                 assertEquals(
                     "ComponentName package mismatched",
@@ -459,7 +459,7 @@
         // Assertions happen in the callback we get from what we pass into
         // #findLastActiveTasksAndRunCallback
         val taskConsumer =
-            Consumer<List<Task>> {
+            Consumer<Array<Task>> {
                 assertEquals("Expected array length 2", 2, it.size)
                 assertEquals(
                     "ComponentName package mismatched",
@@ -532,8 +532,8 @@
         // Assertions happen in the callback we get from what we pass into
         // #findLastActiveTasksAndRunCallback
         val taskConsumer =
-            Consumer<List<Task>> {
-                assertEquals("Expected array length 1", 1, it.size)
+            Consumer<Array<Task>> {
+                assertEquals("Expected array length 2", 2, it.size)
                 assertEquals("Found wrong task", it[0], groupTask2.task1)
             }
 
diff --git a/res/color-night-v31/folder_preview_dark.xml b/res/color-night-v31/folder_preview_dark.xml
index 644d61a..6dd20a1 100644
--- a/res/color-night-v31/folder_preview_dark.xml
+++ b/res/color-night-v31/folder_preview_dark.xml
@@ -16,5 +16,5 @@
 <selector xmlns:android="http://schemas.android.com/apk/res/android" >
     <item
         android:color="@android:color/system_neutral1_900"
-        android:lStar="17" />
+        android:lStar="12" />
 </selector>
diff --git a/res/drawable-v31/bg_deferred_app_widget.xml b/res/drawable-v31/bg_deferred_app_widget.xml
deleted file mode 100644
index a08998d..0000000
--- a/res/drawable-v31/bg_deferred_app_widget.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  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.
--->
-<inset xmlns:android="http://schemas.android.com/apk/res/android"
-    android:inset="8dp">
-    <shape android:shape="rectangle">
-        <corners android:radius="@android:dimen/system_app_widget_background_radius" />
-        <solid android:color="#77000000" />
-    </shape>
-</inset>
diff --git a/res/drawable/bg_deferred_app_widget.xml b/res/drawable/bg_deferred_app_widget.xml
deleted file mode 100644
index 07bae48..0000000
--- a/res/drawable/bg_deferred_app_widget.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2015, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<inset
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:inset="8dp">
-    <color android:color="#77000000" />
-</inset>
diff --git a/res/drawable/bg_rounded_corner_bottom_sheet_handle.xml b/res/drawable/bg_rounded_corner_bottom_sheet_handle.xml
index c502178..379e0e5 100644
--- a/res/drawable/bg_rounded_corner_bottom_sheet_handle.xml
+++ b/res/drawable/bg_rounded_corner_bottom_sheet_handle.xml
@@ -17,6 +17,6 @@
 <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/colorSurfaceVariant"/>
+    <solid android:color="?androidprv:attr/materialColorOutlineVariant"/>
     <corners android:radius="@dimen/bottom_sheet_handle_corner_radius" />
 </shape>
diff --git a/res/drawable/private_space_install_app_icon.xml b/res/drawable/private_space_install_app_icon.xml
new file mode 100644
index 0000000..4c167ba
--- /dev/null
+++ b/res/drawable/private_space_install_app_icon.xml
@@ -0,0 +1,31 @@
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="60dp"
+    android:height="60dp"
+    android:viewportWidth="60"
+    android:viewportHeight="60">
+    <group>
+        <clip-path
+            android:pathData="M30 0H30A30 30 0 0 1 60 30V30A30 30 0 0 1 30 60H30A30 30 0 0 1 0 30V30A30 30 0 0 1 30 0Z" />
+        <path
+            android:pathData="M30 0H30A30 30 0 0 1 60 30V30A30 30 0 0 1 30 60H30A30 30 0 0 1 0 30V30A30 30 0 0 1 30 0Z"
+            android:fillColor="@color/material_color_surface_bright" />
+        <path
+            android:pathData="M29 31h-6v-2h6v-6h2v6h6v2h-6v6h-2v-6Z"
+            android:fillColor="@color/material_color_on_surface_variant" />
+    </group>
+</vector>
diff --git a/res/drawable/widget_internal_focus_bg.xml b/res/drawable/widget_internal_focus_bg.xml
index 4d4bea6..b1f45a4 100644
--- a/res/drawable/widget_internal_focus_bg.xml
+++ b/res/drawable/widget_internal_focus_bg.xml
@@ -23,6 +23,7 @@
     <item android:state_selected="true">
         <shape android:shape="rectangle">
             <stroke android:color="#fff" android:width="2dp" />
+            <corners android:radius="@dimen/focus_outline_radius" />
         </shape>
     </item>
 </selector>
\ No newline at end of file
diff --git a/res/layout/work_apps_edu.xml b/res/layout/work_apps_edu.xml
index eeb7f4f..f557fb6 100644
--- a/res/layout/work_apps_edu.xml
+++ b/res/layout/work_apps_edu.xml
@@ -19,7 +19,7 @@
     android:paddingTop="@dimen/work_edu_card_margin"
     android:paddingBottom="@dimen/work_edu_card_bottom_margin"
     android:gravity="center">
-    <RelativeLayout
+    <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:orientation="horizontal"
@@ -28,40 +28,33 @@
         android:paddingEnd="@dimen/work_card_margin"
         android:paddingStart="@dimen/work_card_margin"
         android:paddingTop="@dimen/work_card_margin"
+        android:paddingBottom="@dimen/work_card_margin"
         android:id="@+id/wrapper">
         <TextView
             style="@style/PrimaryHeadline"
             android:textColor="?android:attr/textColorPrimary"
             android:id="@+id/work_apps_paused_title"
-            android:layout_width="wrap_content"
+            android:layout_width="0dp"
             android:layout_height="wrap_content"
-            android:layout_marginBottom="@dimen/work_card_margin"
-            android:layout_marginEnd="@dimen/work_card_margin"
+            android:layout_weight="1"
+            android:paddingEnd="@dimen/work_edu_card_text_end_margin"
             android:text="@string/work_profile_edu_work_apps"
             android:textDirection="locale"
             android:textSize="18sp" />
-        <RelativeLayout
-            android:layout_width="match_parent"
-            android:layout_height="@dimen/padded_rounded_button_height"
-            android:orientation="horizontal">
-            <FrameLayout
-                android:layout_width="@dimen/rounded_button_width"
-                android:layout_height="@dimen/rounded_button_width"
-                android:layout_alignParentEnd="true"
-                android:background="@drawable/rounded_action_button"
-                android:padding="@dimen/rounded_button_padding">
-                <ImageButton
-                    android:id="@+id/action_btn"
-                    android:layout_width="@dimen/x_icon_size"
-                    android:layout_height="@dimen/x_icon_size"
-                    android:layout_gravity="center"
-                    android:padding="@dimen/x_icon_padding"
-                    android:contentDescription="@string/accessibility_close"
-                    android:src="@drawable/ic_remove_no_shadow" />
-            </FrameLayout>
-        </RelativeLayout>
-    </RelativeLayout>
-
-
-
-</com.android.launcher3.allapps.WorkEduCard>
\ No newline at end of file
+        <FrameLayout
+            android:layout_width="@dimen/rounded_button_width"
+            android:layout_height="@dimen/rounded_button_width"
+            android:background="@drawable/rounded_action_button"
+            android:padding="@dimen/rounded_button_padding">
+            <ImageButton
+                android:id="@+id/action_btn"
+                android:layout_width="@dimen/x_icon_size"
+                android:layout_height="@dimen/x_icon_size"
+                android:layout_gravity="center"
+                android:contentDescription="@string/accessibility_close"
+                android:padding="@dimen/x_icon_padding"
+                android:background="@android:color/transparent"
+                android:src="@drawable/ic_remove_no_shadow" />
+        </FrameLayout>
+    </LinearLayout>
+</com.android.launcher3.allapps.WorkEduCard>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 8d84c90..e54539f 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -189,6 +189,10 @@
          defaults to 2 * numAllAppsColumns -->
         <attr name="numExtendedAllAppsColumns" format="integer" />
 
+        <!-- Number of rows to calculate the cell height for all apps when it's necessary.
+          Defaults to numRows. Requires FeatureFlags.ENABLE_RESPONSIVE_WORKSPACE to be enabled. -->
+        <attr name="numAllAppsRowsForCellHeightCalculation" format="integer" />
+
         <!-- numHotseatIcons defaults to numColumns, if not specified -->
         <attr name="numHotseatIcons" format="integer" />
         <!-- Number of icons to use when extending the hotseat size,
@@ -219,26 +223,32 @@
         <!-- File that contains the specs for the workspace.
         Needs FeatureFlags.ENABLE_RESPONSIVE_WORKSPACE enabled -->
         <attr name="workspaceSpecsId" format="reference" />
+        <!-- defaults to workspaceSpecsId, if not specified -->
         <attr name="workspaceSpecsTwoPanelId" format="reference" />
         <!-- File that contains the specs for all apps.
         Needs FeatureFlags.ENABLE_RESPONSIVE_WORKSPACE enabled -->
         <attr name="allAppsSpecsId" format="reference" />
+        <!-- defaults to allAppsSpecsId, if not specified -->
         <attr name="allAppsSpecsTwoPanelId" format="reference" />
         <!-- File that contains the specs for the workspace.
         Needs FeatureFlags.ENABLE_RESPONSIVE_WORKSPACE enabled -->
         <attr name="folderSpecsId" format="reference" />
+        <!-- defaults to folderSpecsId, if not specified -->
         <attr name="folderSpecsTwoPanelId" format="reference" />
         <!-- File that contains the specs for hotseat bar.
         Needs FeatureFlags.ENABLE_RESPONSIVE_WORKSPACE enabled -->
         <attr name="hotseatSpecsId" format="reference" />
+        <!-- defaults to hotseatSpecsId, if not specified -->
         <attr name="hotseatSpecsTwoPanelId" format="reference" />
         <!-- File that contains the specs for workspace icon and text size.
         Needs FeatureFlags.ENABLE_RESPONSIVE_WORKSPACE enabled -->
         <attr name="workspaceCellSpecsId" format="reference" />
+        <!-- defaults to workspaceCellSpecsId, if not specified -->
         <attr name="workspaceCellSpecsTwoPanelId" format="reference" />
         <!-- File that contains the specs for all apps icon and text size.
         Needs FeatureFlags.ENABLE_RESPONSIVE_WORKSPACE enabled -->
         <attr name="allAppsCellSpecsId" format="reference" />
+        <!-- defaults to allAppsCellSpecsId, if not specified -->
         <attr name="allAppsCellSpecsTwoPanelId" format="reference" />
 
         <!-- By default all categories are enabled -->
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 53d7d0c..db9631a 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -72,7 +72,7 @@
     <color name="folder_background_dark">#1F2020</color>
 
     <color name="folder_preview_light">#7FCFFF</color>
-    <color name="folder_preview_dark">#2A2A2A</color>
+    <color name="folder_preview_dark">#1E1F20</color>
 
     <color name="folder_pagination_color_light">#0B57D0</color>
     <color name="folder_pagination_color_dark">#A8C7FA</color>
diff --git a/res/values/config.xml b/res/values/config.xml
index 2980635..33eb4c7 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -201,7 +201,6 @@
 
     <!-- Swipe back to home related -->
     <dimen name="swipe_back_window_scale_x_margin">10dp</dimen>
-    <dimen name="swipe_back_window_max_delta_y">160dp</dimen>
     <dimen name="swipe_back_window_corner_radius">40dp</dimen>
 
     <!-- The duration of the bottom sheet opening and closing animation -->
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 62ce256..603e697 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -103,6 +103,7 @@
     <dimen name="all_apps_search_bar_content_overlap">24dp</dimen>
     <dimen name="all_apps_search_bar_bottom_padding">30dp</dimen>
     <dimen name="all_apps_empty_search_message_top_offset">40dp</dimen>
+    <dimen name="all_apps_search_top_row_extra_height">4dp</dimen>
     <dimen name="all_apps_header_pill_height">48dp</dimen>
     <dimen name="all_apps_header_pill_corner_radius">12dp</dimen>
     <dimen name="all_apps_header_tab_height">48dp</dimen>
@@ -155,6 +156,7 @@
     <dimen name="work_edu_card_margin">16dp</dimen>
     <dimen name="work_edu_card_radius">16dp</dimen>
     <dimen name="work_edu_card_bottom_margin">26dp</dimen>
+    <dimen name="work_edu_card_text_end_margin">32dp</dimen>
     <dimen name="work_apps_paused_button_stroke">1dp</dimen>
 
     <dimen name="work_card_margin">24dp</dimen>
@@ -368,6 +370,11 @@
     <!-- Taskbar related (placeholders to compile in Launcher3 without Quickstep) -->
     <dimen name="taskbar_size">0dp</dimen>
     <dimen name="taskbar_phone_size">@*android:dimen/navigation_bar_frame_height</dimen>
+    <dimen name="taskbar_phone_home_button_size">80dp</dimen>
+    <dimen name="taskbar_phone_content_padding">8dp</dimen>
+    <dimen name="taskbar_phone_rounded_corner_content_margin">
+        @*android:dimen/rounded_corner_content_padding
+    </dimen>
     <dimen name="taskbar_stashed_size">0dp</dimen>
     <dimen name="qsb_widget_height">0dp</dimen>
     <dimen name="qsb_shadow_height">0dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 6a4a9a4..956d24d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -461,6 +461,10 @@
     <string name="ps_container_lock_unlock_button">Lock/Unlock Private Space</string>
     <!-- Description for Private Space Transition button -->
     <string name="ps_container_transition">Private Space Transitioning</string>
+    <!-- Title for Private Space install app icon -->
+    <string name="ps_add_button_label">Install apps</string>
+    <!-- Content description for install app icon -->
+    <string name="ps_add_button_content_description">Install apps to Private Space</string>
 
     <!-- Strings for bubble bar -->
     <!-- content description for the overflow bubble [CHAR_LIMIT=none] -->
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index f8ed4df..b6f6615 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -39,7 +39,6 @@
 import com.android.launcher3.util.DisplayController.Info;
 import com.android.launcher3.util.OnColorHintListener;
 import com.android.launcher3.util.Themes;
-import com.android.launcher3.util.TraceHelper;
 import com.android.launcher3.util.WallpaperColorHints;
 import com.android.launcher3.util.WindowBounds;
 
@@ -55,16 +54,12 @@
     public static final Object AUTO_CANCEL_ACTION_MODE = new Object();
 
     private ActionMode mCurrentActionMode;
-    protected boolean mIsSafeModeEnabled;
 
     private int mThemeRes = R.style.AppTheme;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-
-        mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
-                () -> getPackageManager().isSafeMode());
         DisplayController.INSTANCE.get(this).addChangeListener(this);
 
         // Update theme
@@ -183,6 +178,6 @@
 
     @Override
     public boolean isAppBlockedForSafeMode() {
-        return mIsSafeModeEnabled;
+        return LauncherAppState.getInstance(this).isSafeModeEnabled();
     }
 }
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index bd271b9..5b497f2 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -933,6 +933,8 @@
             if (mIcon instanceof PreloadIconDrawable) {
                 preloadIconDrawable = (PreloadIconDrawable) mIcon;
                 preloadIconDrawable.setLevel(progressLevel);
+                // TODO(b/302115555): For archived apps, show icon as disabled if active session
+                //  exists.
                 preloadIconDrawable.setIsDisabled(info.getProgressLevel() == 0);
             } else {
                 preloadIconDrawable = makePreloadIcon();
@@ -958,6 +960,7 @@
         final PreloadIconDrawable preloadDrawable = newPendingIcon(getContext(), info);
 
         preloadDrawable.setLevel(progressLevel);
+        // TODO(b/302115555): For archived apps, show icon as disabled if active session exists.
         preloadDrawable.setIsDisabled(info.getProgressLevel() == 0);
         return preloadDrawable;
     }
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 1ca7da9..834ba04 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -78,7 +78,6 @@
 public class DeviceProfile {
 
     private static final int DEFAULT_DOT_SIZE = 100;
-    private static final float ALL_APPS_TABLET_MAX_ROWS = 5.5f;
     private static final float MIN_FOLDER_TEXT_SIZE_SP = 16f;
     private static final float MIN_WIDGET_PADDING_DP = 6f;
 
@@ -641,8 +640,8 @@
                     DimensionType.WIDTH, numShownAllAppsColumns, availableWidthPx,
                     mResponsiveWorkspaceWidthSpec);
             mResponsiveAllAppsHeightSpec = allAppsSpecs.getCalculatedSpec(responsiveAspectRatio,
-                    DimensionType.HEIGHT, inv.numRows,  heightPx - mInsets.top,
-                    mResponsiveWorkspaceHeightSpec);
+                    DimensionType.HEIGHT, inv.numAllAppsRowsForCellHeightCalculation,
+                    heightPx - mInsets.top, mResponsiveWorkspaceHeightSpec);
 
             ResponsiveSpecsProvider folderSpecs = ResponsiveSpecsProvider.create(
                     new ResourceHelper(context,
@@ -734,14 +733,9 @@
             hotseatBorderSpace = cellLayoutBorderSpacePx.y;
         }
 
-        // AllApps height calculation depends on updated cellSize
         if (isTablet) {
-            int collapseHandleHeight =
-                    res.getDimensionPixelOffset(R.dimen.bottom_sheet_handle_area_height);
-            int contentHeight = heightPx - collapseHandleHeight - hotseatQsbHeight;
-            int targetContentHeight = (int) (allAppsCellHeightPx * ALL_APPS_TABLET_MAX_ROWS);
-            allAppsPadding.top = Math.max(mInsets.top, contentHeight - targetContentHeight);
-            allAppsShiftRange = heightPx - allAppsPadding.top;
+            allAppsPadding.top = mInsets.top;
+            allAppsShiftRange = heightPx;
         } else {
             allAppsPadding.top = 0;
             allAppsShiftRange =
@@ -789,14 +783,16 @@
      * width of the hotseat.
      */
     private int calculateQsbWidth(int hotseatBorderSpace) {
+        int iconExtraSpacePx = iconSizePx - getIconVisibleSizePx(iconSizePx);
         if (isQsbInline) {
             int columns = getPanelCount() * inv.numColumns;
             return getIconToIconWidthForColumns(columns)
                     - iconSizePx * numShownHotseatIcons
-                    - hotseatBorderSpace * numShownHotseatIcons;
+                    - hotseatBorderSpace * numShownHotseatIcons
+                    - iconExtraSpacePx;
         } else {
             int columns = inv.hotseatColumnSpan[mTypeIndex];
-            return getIconToIconWidthForColumns(columns);
+            return getIconToIconWidthForColumns(columns) - iconExtraSpacePx;
         }
     }
 
@@ -992,16 +988,6 @@
         float workspaceCellPaddingY = getCellSize().y - iconSizePx - iconDrawablePaddingPx
                 - iconTextHeight;
 
-        if (mIsResponsiveGrid) {
-            iconTextSizePx = 0;
-            iconDrawablePaddingPx = 0;
-            int iconSizeWithOverlap = getIconSizeWithOverlap(iconSizePx);
-            cellYPaddingPx = Math.max(0, getCellSize().y - iconSizeWithOverlap) / 2;
-            autoResizeAllAppsCells();
-
-            return;
-        }
-
         // We want enough space so that the text is closer to its corresponding icon.
         if (workspaceCellPaddingY < iconTextHeight) {
             iconTextSizePx = 0;
@@ -1074,11 +1060,8 @@
     }
 
     private int getNormalizedIconDrawablePadding(int iconSizePx, int iconDrawablePadding) {
-        // TODO(b/235886078): workaround needed because of this bug
-        // Icons are 10% larger on XML than their visual size,
-        // so remove that extra space to get labels closer to the correct padding
-        int iconVisibleSizePx = Math.round(ICON_VISIBLE_AREA_FACTOR * iconSizePx);
-        return Math.max(0, iconDrawablePadding - ((iconSizePx - iconVisibleSizePx) / 2));
+        return Math.max(0, iconDrawablePadding
+                - ((iconSizePx - getIconVisibleSizePx(iconSizePx)) / 2));
     }
 
     private int getNormalizedIconDrawablePadding() {
@@ -1091,8 +1074,7 @@
         // so remove that extra space to get labels closer to the correct padding
         int drawablePadding = (folderCellHeightPx - folderChildIconSizePx - textHeight) / 3;
 
-        int iconVisibleSizePx = Math.round(ICON_VISIBLE_AREA_FACTOR * folderChildIconSizePx);
-        int iconSizeDiff = folderChildIconSizePx - iconVisibleSizePx;
+        int iconSizeDiff = folderChildIconSizePx - getIconVisibleSizePx(folderChildIconSizePx);
         return Math.max(0, drawablePadding - iconSizeDiff / 2);
     }
 
@@ -1123,25 +1105,28 @@
                 iconSizePx = mIconSizeSteps.getIconSmallerThan(cellWidthPx);
             }
 
-            iconDrawablePaddingPx = getNormalizedIconDrawablePadding();
+            if (isVerticalLayout) {
+                iconDrawablePaddingPx = 0;
+                iconTextSizePx = 0;
+            } else {
+                iconDrawablePaddingPx = getNormalizedIconDrawablePadding();
+            }
 
             CellContentDimensions cellContentDimensions = new CellContentDimensions(iconSizePx,
                     iconDrawablePaddingPx,
                     iconTextSizePx);
-            if (isVerticalLayout) {
-                if (cellHeightPx < iconSizePx) {
-                    cellContentDimensions.setIconSizePx(
-                            mIconSizeSteps.getIconSmallerThan(cellHeightPx));
-                }
-            } else {
-                cellContentDimensions.resizeToFitCellHeight(cellHeightPx, mIconSizeSteps);
-            }
+            int cellContentHeight = cellContentDimensions.resizeToFitCellHeight(cellHeightPx,
+                    mIconSizeSteps);
             iconSizePx = cellContentDimensions.getIconSizePx();
             iconDrawablePaddingPx = cellContentDimensions.getIconDrawablePaddingPx();
             iconTextSizePx = cellContentDimensions.getIconTextSizePx();
-            int cellContentHeight = cellContentDimensions.getCellContentHeight();
 
-            cellYPaddingPx = Math.max(0, cellHeightPx - cellContentHeight) / 2;
+            if (isVerticalLayout) {
+                cellYPaddingPx = Math.max(0, getCellSize().y - getIconSizeWithOverlap(iconSizePx))
+                        / 2;
+            } else {
+                cellYPaddingPx = Math.max(0, cellHeightPx - cellContentHeight) / 2;
+            }
         } else if (mIsScalableGrid) {
             iconDrawablePaddingPx = (int) (getNormalizedIconDrawablePadding() * iconScale);
             cellWidthPx = pxFromDp(inv.minCellSize[mTypeIndex].x, mMetrics, scale);
@@ -1225,7 +1210,7 @@
             updateAllAppsIconSize(scale, res);
         }
         updateAllAppsContainerWidth();
-        if (isVerticalBarLayout()) {
+        if (isVerticalLayout && !mIsResponsiveGrid) {
             hideWorkspaceLabelsIfNotEnoughSpace();
         }
         if (FeatureFlags.enableTwolineAllapps()) {
@@ -1349,7 +1334,7 @@
 
         if (allAppsCellHeightPx < cellContentDimensions.getCellContentHeight()) {
             if (isVerticalBarLayout()) {
-                if (allAppsCellHeightPx < iconSizePx) {
+                if (allAppsCellHeightPx < allAppsIconSizePx) {
                     cellContentDimensions.setIconSizePx(
                             mIconSizeSteps.getIconSmallerThan(allAppsCellHeightPx));
                 }
@@ -1363,6 +1348,10 @@
         }
 
         allAppsCellHeightPx += mResponsiveAllAppsHeightSpec.getGutterPx();
+
+        if (isVerticalBarLayout()) {
+            autoResizeAllAppsCells();
+        }
     }
 
     /**
@@ -1788,7 +1777,8 @@
             }
 
         } else if (mIsScalableGrid) {
-            int sideSpacing = (availableWidthPx - hotseatQsbWidth) / 2;
+            int iconExtraSpacePx = iconSizePx - getIconVisibleSizePx(iconSizePx);
+            int sideSpacing = (availableWidthPx - (hotseatQsbWidth + iconExtraSpacePx)) / 2;
             hotseatBarPadding.set(sideSpacing,
                     0,
                     sideSpacing,
@@ -1827,13 +1817,24 @@
                 availableWidthPx - allAppsSpacing,
                 0 /* borderSpace */,
                 numShownAllAppsColumns);
-        int iconVisibleSize = Math.round(ICON_VISIBLE_AREA_FACTOR * allAppsIconSizePx);
-        int iconAlignmentMargin = (cellWidth - iconVisibleSize) / 2;
+        int iconAlignmentMargin = (cellWidth - getIconVisibleSizePx(allAppsIconSizePx)) / 2;
 
         return (Utilities.isRtl(context.getResources()) ? allAppsPadding.right
                 : allAppsPadding.left) + iconAlignmentMargin;
     }
 
+    /**
+     * TODO(b/235886078): workaround needed because of this bug
+     * Icons are 10% larger on XML than their visual size, so remove that extra space to get
+     * some dimensions correct.
+     *
+     * When this bug is resolved this method will no longer be needed and we would be able to
+     * replace all instances where this method is called with iconSizePx.
+     */
+    private int getIconVisibleSizePx(int iconSizePx) {
+        return Math.round(ICON_VISIBLE_AREA_FACTOR * iconSizePx);
+    }
+
     private int getAdditionalQsbSpace() {
         return isQsbInline ? hotseatQsbWidth + hotseatBorderSpace : 0;
     }
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 5721ed3..42d4d50 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -170,6 +170,7 @@
      * Number of columns in the all apps list.
      */
     public int numAllAppsColumns;
+    public int numAllAppsRowsForCellHeightCalculation;
     public int numDatabaseAllAppsColumns;
     public @StyleRes int allAppsStyle;
 
@@ -247,7 +248,7 @@
     public InvariantDeviceProfile(Context context, String gridName) {
         String newName = initGrid(context, gridName);
         if (newName == null || !newName.equals(gridName)) {
-            throw new IllegalArgumentException("Unknown grid name");
+            throw new IllegalArgumentException("Unknown grid name: " + gridName);
         }
     }
 
@@ -393,6 +394,8 @@
         workspaceCellSpecsTwoPanelId = closestProfile.mWorkspaceCellSpecsTwoPanelId;
         allAppsCellSpecsId = closestProfile.mAllAppsCellSpecsId;
         allAppsCellSpecsTwoPanelId = closestProfile.mAllAppsCellSpecsTwoPanelId;
+        numAllAppsRowsForCellHeightCalculation =
+                closestProfile.mNumAllAppsRowsForCellHeightCalculation;
         this.deviceType = deviceType;
 
         inlineNavButtonsEndSpacing = closestProfile.inlineNavButtonsEndSpacing;
@@ -423,6 +426,7 @@
         allAppsStyle = closestProfile.allAppsStyle;
 
         numAllAppsColumns = closestProfile.numAllAppsColumns;
+
         numDatabaseAllAppsColumns = deviceType == TYPE_MULTI_DISPLAY
                 ? closestProfile.numDatabaseAllAppsColumns : closestProfile.numAllAppsColumns;
 
@@ -821,6 +825,7 @@
 
         private final @StyleRes int allAppsStyle;
         private final int numAllAppsColumns;
+        private final int mNumAllAppsRowsForCellHeightCalculation;
         private final int numDatabaseAllAppsColumns;
         private final int numHotseatIcons;
         private final int numDatabaseHotseatIcons;
@@ -943,34 +948,37 @@
                         R.styleable.GridDisplayOption_workspaceSpecsId, INVALID_RESOURCE_HANDLE);
                 mWorkspaceSpecsTwoPanelId = a.getResourceId(
                         R.styleable.GridDisplayOption_workspaceSpecsTwoPanelId,
-                        INVALID_RESOURCE_HANDLE);
+                        mWorkspaceSpecsId);
                 mAllAppsSpecsId = a.getResourceId(
                         R.styleable.GridDisplayOption_allAppsSpecsId, INVALID_RESOURCE_HANDLE);
                 mAllAppsSpecsTwoPanelId = a.getResourceId(
                         R.styleable.GridDisplayOption_allAppsSpecsTwoPanelId,
-                        INVALID_RESOURCE_HANDLE);
+                        mAllAppsSpecsId);
                 mFolderSpecsId = a.getResourceId(
                         R.styleable.GridDisplayOption_folderSpecsId, INVALID_RESOURCE_HANDLE);
                 mFolderSpecsTwoPanelId = a.getResourceId(
                         R.styleable.GridDisplayOption_folderSpecsTwoPanelId,
-                        INVALID_RESOURCE_HANDLE);
+                        mFolderSpecsId);
                 mHotseatSpecsId = a.getResourceId(
                         R.styleable.GridDisplayOption_hotseatSpecsId, INVALID_RESOURCE_HANDLE);
                 mHotseatSpecsTwoPanelId = a.getResourceId(
                         R.styleable.GridDisplayOption_hotseatSpecsTwoPanelId,
-                        INVALID_RESOURCE_HANDLE);
+                        mHotseatSpecsId);
                 mWorkspaceCellSpecsId = a.getResourceId(
                         R.styleable.GridDisplayOption_workspaceCellSpecsId,
                         INVALID_RESOURCE_HANDLE);
                 mWorkspaceCellSpecsTwoPanelId = a.getResourceId(
                         R.styleable.GridDisplayOption_workspaceCellSpecsTwoPanelId,
-                        INVALID_RESOURCE_HANDLE);
+                        mWorkspaceCellSpecsId);
                 mAllAppsCellSpecsId = a.getResourceId(
                         R.styleable.GridDisplayOption_allAppsCellSpecsId,
                         INVALID_RESOURCE_HANDLE);
                 mAllAppsCellSpecsTwoPanelId = a.getResourceId(
                         R.styleable.GridDisplayOption_allAppsCellSpecsTwoPanelId,
-                        INVALID_RESOURCE_HANDLE);
+                        mAllAppsCellSpecsId);
+                mNumAllAppsRowsForCellHeightCalculation = a.getInt(
+                        R.styleable.GridDisplayOption_numAllAppsRowsForCellHeightCalculation,
+                        numRows);
             } else {
                 mWorkspaceSpecsId = INVALID_RESOURCE_HANDLE;
                 mWorkspaceSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
@@ -984,6 +992,7 @@
                 mWorkspaceCellSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
                 mAllAppsCellSpecsId = INVALID_RESOURCE_HANDLE;
                 mAllAppsCellSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
+                mNumAllAppsRowsForCellHeightCalculation = numRows;
             }
 
             int inlineForRotation = a.getInt(R.styleable.GridDisplayOption_inlineQsb,
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 11dc6e2..152ad5f 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -89,7 +89,6 @@
 import static com.android.launcher3.logging.StatsLogManager.StatsLatencyLogger.LatencyType.WARM;
 import static com.android.launcher3.model.ItemInstallQueue.FLAG_ACTIVITY_PAUSED;
 import static com.android.launcher3.model.ItemInstallQueue.FLAG_DRAG_AND_DROP;
-import static com.android.launcher3.model.data.LauncherAppWidgetInfo.CUSTOM_WIDGET_ID;
 import static com.android.launcher3.popup.SystemShortcut.APP_INFO;
 import static com.android.launcher3.popup.SystemShortcut.INSTALL;
 import static com.android.launcher3.popup.SystemShortcut.WIDGETS;
@@ -196,7 +195,6 @@
 import com.android.launcher3.model.ItemInstallQueue;
 import com.android.launcher3.model.ModelWriter;
 import com.android.launcher3.model.StringCache;
-import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
@@ -208,7 +206,6 @@
 import com.android.launcher3.popup.ArrowPopup;
 import com.android.launcher3.popup.PopupDataProvider;
 import com.android.launcher3.popup.SystemShortcut;
-import com.android.launcher3.qsb.QsbContainerView;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.statemanager.StateManager.StateHandler;
 import com.android.launcher3.statemanager.StatefulActivity;
@@ -252,6 +249,8 @@
 import com.android.launcher3.widget.PendingAddWidgetInfo;
 import com.android.launcher3.widget.PendingAppWidgetHostView;
 import com.android.launcher3.widget.WidgetAddFlowHandler;
+import com.android.launcher3.widget.WidgetInflater;
+import com.android.launcher3.widget.WidgetInflater.InflationResult;
 import com.android.launcher3.widget.WidgetManagerHelper;
 import com.android.launcher3.widget.custom.CustomWidgetManager;
 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
@@ -333,6 +332,7 @@
 
     private WidgetManagerHelper mAppWidgetManager;
     private LauncherWidgetHolder mAppWidgetHolder;
+    private WidgetInflater mWidgetInflater;
 
     private final int[] mTmpAddItemCellCoordinates = new int[2];
 
@@ -517,6 +517,7 @@
         setupViews();
 
         mAppWidgetManager = new WidgetManagerHelper(this);
+        mWidgetInflater = new WidgetInflater(this);
         mAppWidgetHolder = createAppWidgetHolder();
         mAppWidgetHolder.startListening();
         mAppWidgetHolder.addProviderChangeListener(() -> refreshAndBindWidgetsForPackageUser(null));
@@ -1011,7 +1012,7 @@
         AppWidgetHostView boundWidget = null;
         if (resultCode == RESULT_OK) {
             animationType = Workspace.COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION;
-            final AppWidgetHostView layout = mAppWidgetHolder.createView(this, appWidgetId,
+            final AppWidgetHostView layout = mAppWidgetHolder.createView(appWidgetId,
                     requestArgs.getWidgetHandler().getProviderInfo(this));
             boundWidget = layout;
             onCompleteRunnable = () -> {
@@ -1464,7 +1465,7 @@
 
         if (hostView == null) {
             // Perform actual inflation because we're live
-            hostView = mAppWidgetHolder.createView(this, appWidgetId, appWidgetInfo);
+            hostView = mAppWidgetHolder.createView(appWidgetId, appWidgetInfo);
         }
 
         LauncherAppWidgetInfo launcherInfo;
@@ -1715,11 +1716,7 @@
         mModel.removeCallbacks(this);
         mRotationHelper.destroy();
 
-        try {
-            mAppWidgetHolder.stopListening();
-        } catch (NullPointerException ex) {
-            Log.w(TAG, "problem while stopping AppWidgetHost during Launcher destruction", ex);
-        }
+        mAppWidgetHolder.stopListening();
         mAppWidgetHolder.destroy();
 
         TextKeyListener.getInstance().release();
@@ -2313,162 +2310,30 @@
 
     private View inflateAppWidget(LauncherAppWidgetInfo item,
             @Nullable LauncherRestoreEventLogger restoreEventLogger) {
-        if (item.hasOptionFlag(LauncherAppWidgetInfo.OPTION_SEARCH_WIDGET)) {
-            item.providerName = QsbContainerView.getSearchComponentName(this);
-            if (item.providerName == null) {
-                getModelWriter().deleteItemFromDatabase(item,
-                        "search widget removed because search component cannot be found");
-                return null;
-            }
-        }
-        final AppWidgetHostView view;
-        if (mIsSafeModeEnabled) {
-            view = new PendingAppWidgetHostView(this, item, mIconCache, true);
-            prepareAppWidget(view, item);
-            return view;
-        }
-
         TraceHelper.INSTANCE.beginSection("BIND_WIDGET_id=" + item.appWidgetId);
         try {
-            final LauncherAppWidgetProviderInfo appWidgetInfo;
-            String removalReason = "";
-            if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) {
-                // If the provider is not ready, bind as a pending widget.
-                appWidgetInfo = null;
-                removalReason = "the provider isn't ready.";
-            } else if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
-                // The widget id is not valid. Try to find the widget based on the provider info.
-                appWidgetInfo = mAppWidgetManager.findProvider(item.providerName, item.user);
-                if (appWidgetInfo == null) {
-                    if (WidgetsModel.GO_DISABLE_WIDGETS) {
-                        removalReason = "widgets are disabled on go device.";
-                    } else {
-                        removalReason =
-                                "WidgetManagerHelper cannot find a provider from provider info.";
-                    }
+            InflationResult inflationResult = mWidgetInflater.inflateAppWidget(item);
+            if (inflationResult.getType() == WidgetInflater.TYPE_DELETE) {
+                getModelWriter().deleteItemFromDatabase(item, inflationResult.getReason());
+                if (restoreEventLogger != null) {
+                    restoreEventLogger.logSingleFavoritesItemRestoreFailed(
+                            ITEM_TYPE_APPWIDGET, RESTORE_ERROR_BIND_FAILURE);
                 }
-            } else {
-                appWidgetInfo = mAppWidgetManager.getLauncherAppWidgetInfo(item.appWidgetId,
-                        item.getTargetComponent());
-                if (appWidgetInfo == null) {
-                    if (item.appWidgetId <= CUSTOM_WIDGET_ID) {
-                        removalReason =
-                                "CustomWidgetManager cannot find provider from that widget id.";
-                    } else {
-                        removalReason = "AppWidgetManager cannot find provider for that widget id."
-                                + " It could be because AppWidgetService is not available, or the"
-                                + " appWidgetId has not been bound to a the provider yet, or you"
-                                + " don't have access to that appWidgetId.";
-                    }
-                }
+                return null;
             }
 
-            // If the provider is ready, but the width is not yet restored, try to restore it.
-            if (!item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
-                    && (item.restoreStatus != LauncherAppWidgetInfo.RESTORE_COMPLETED)) {
-                if (appWidgetInfo == null) {
-                    getModelWriter().deleteItemFromDatabase(item,
-                            "Removing restored widget: id=" + item.appWidgetId
-                            + " belongs to component " + item.providerName + " user " + item.user
-                            + ", as the provider is null and " + removalReason);
-                    if (restoreEventLogger != null) {
-                        restoreEventLogger.logSingleFavoritesItemRestoreFailed(
-                                ITEM_TYPE_APPWIDGET, RESTORE_ERROR_BIND_FAILURE);
-                    }
-                    return null;
-                }
-
-                // If we do not have a valid id, try to bind an id.
-                if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
-                    if (!item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_ALLOCATED)) {
-                        // Id has not been allocated yet. Allocate a new id.
-                        item.appWidgetId = mAppWidgetHolder.allocateAppWidgetId();
-                        item.restoreStatus |= LauncherAppWidgetInfo.FLAG_ID_ALLOCATED;
-
-                        // Also try to bind the widget. If the bind fails, the user will be shown
-                        // a click to setup UI, which will ask for the bind permission.
-                        PendingAddWidgetInfo pendingInfo =
-                                new PendingAddWidgetInfo(appWidgetInfo, item.sourceContainer);
-                        pendingInfo.spanX = item.spanX;
-                        pendingInfo.spanY = item.spanY;
-                        pendingInfo.minSpanX = item.minSpanX;
-                        pendingInfo.minSpanY = item.minSpanY;
-                        Bundle options = pendingInfo.getDefaultSizeOptions(this);
-
-                        boolean isDirectConfig =
-                                item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG);
-                        if (isDirectConfig && item.bindOptions != null) {
-                            Bundle newOptions = item.bindOptions.getExtras();
-                            if (options != null) {
-                                newOptions.putAll(options);
-                            }
-                            options = newOptions;
-                        }
-                        boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
-                                item.appWidgetId, appWidgetInfo, options);
-
-                        // We tried to bind once. If we were not able to bind, we would need to
-                        // go through the permission dialog, which means we cannot skip the config
-                        // activity.
-                        item.bindOptions = null;
-                        item.restoreStatus &= ~LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG;
-
-                        // Bind succeeded
-                        if (success) {
-                            // If the widget has a configure activity, it is still needs to set it
-                            // up, otherwise the widget is ready to go.
-                            item.restoreStatus = (appWidgetInfo.configure == null) || isDirectConfig
-                                    ? LauncherAppWidgetInfo.RESTORE_COMPLETED
-                                    : LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
-                        }
-
-                        getModelWriter().updateItemInDatabase(item);
-                    }
-                } else if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_UI_NOT_READY)
-                        && (appWidgetInfo.configure == null)) {
-                    // The widget was marked as UI not ready, but there is no configure activity to
-                    // update the UI.
-                    item.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED;
-                    getModelWriter().updateItemInDatabase(item);
-                }
-                else if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_UI_NOT_READY)
-                        && appWidgetInfo.configure != null) {
-                    if (mAppWidgetManager.isAppWidgetRestored(item.appWidgetId)) {
-                        item.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED;
-                        getModelWriter().updateItemInDatabase(item);
-                    }
-                }
+            if (inflationResult.isUpdate()) {
+                getModelWriter().updateItemInDatabase(item);
             }
-
-            if (item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) {
-                // Verify that we own the widget
-                if (appWidgetInfo == null) {
-                    FileLog.e(TAG, "Removing invalid widget: id=" + item.appWidgetId);
-                    getModelWriter().deleteWidgetInfo(item, getAppWidgetHolder(), removalReason);
-                    if (restoreEventLogger != null) {
-                        restoreEventLogger.logSingleFavoritesItemRestoreFailed(
-                                ITEM_TYPE_APPWIDGET, RESTORE_ERROR_BIND_FAILURE);
-                    }
-                    return null;
-                }
-
-                item.minSpanX = appWidgetInfo.minSpanX;
-                item.minSpanY = appWidgetInfo.minSpanY;
-                view = mAppWidgetHolder.createView(this, item.appWidgetId, appWidgetInfo);
-            } else if (!item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)
-                    && appWidgetInfo != null) {
-                mAppWidgetHolder.addPendingView(item.appWidgetId,
-                        new PendingAppWidgetHostView(this, item, mIconCache, false));
-                view = mAppWidgetHolder.createView(this, item.appWidgetId, appWidgetInfo);
-            } else {
-                view = new PendingAppWidgetHostView(this, item, mIconCache, false);
-            }
+            AppWidgetHostView view = inflationResult.getType() == WidgetInflater.TYPE_PENDING
+                    ? new PendingAppWidgetHostView(this, item, inflationResult.getWidgetInfo())
+                    : mAppWidgetHolder.createView(
+                            item.appWidgetId, inflationResult.getWidgetInfo());
             prepareAppWidget(view, item);
+            return view;
         } finally {
             TraceHelper.INSTANCE.endSection();
         }
-
-        return view;
     }
 
     /**
@@ -2478,7 +2343,7 @@
      */
     private LauncherAppWidgetInfo completeRestoreAppWidget(int appWidgetId, int finalRestoreFlag) {
         LauncherAppWidgetHostView view = mWorkspace.getWidgetForAppWidgetId(appWidgetId);
-        if ((view == null) || !(view instanceof PendingAppWidgetHostView)) {
+        if (!(view instanceof PendingAppWidgetHostView)) {
             Log.e(TAG, "Widget update called, when the widget no longer exists.");
             return null;
         }
@@ -2489,8 +2354,9 @@
             info.pendingItemInfo = null;
         }
 
-        if (((PendingAppWidgetHostView) view).isReinflateIfNeeded()) {
-            view.reInflate();
+        PendingAppWidgetHostView pv = (PendingAppWidgetHostView) view;
+        if (pv.isReinflateIfNeeded()) {
+            pv.reInflate();
         }
 
         getModelWriter().updateItemInDatabase(info);
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 9a19526..d4bcd24 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -36,11 +36,7 @@
 import android.content.pm.LauncherApps;
 import android.os.UserHandle;
 import android.util.Log;
-import android.util.SparseArray;
-import android.widget.RemoteViews;
 
-import androidx.annotation.GuardedBy;
-import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.graphics.IconShape;
@@ -60,6 +56,7 @@
 import com.android.launcher3.util.SettingsCache;
 import com.android.launcher3.util.SimpleBroadcastReceiver;
 import com.android.launcher3.util.Themes;
+import com.android.launcher3.util.TraceHelper;
 import com.android.launcher3.widget.custom.CustomWidgetManager;
 
 public class LauncherAppState implements SafeCloseable {
@@ -75,13 +72,9 @@
     private final LauncherIconProvider mIconProvider;
     private final IconCache mIconCache;
     private final InvariantDeviceProfile mInvariantDeviceProfile;
-    private final RunnableList mOnTerminateCallback = new RunnableList();
+    private boolean mIsSafeModeEnabled;
 
-    // WORKAROUND: b/269335387 remove this after widget background listener is enabled
-    /* Array of RemoteViews cached by Launcher process */
-    @GuardedBy("itself")
-    @NonNull
-    public final SparseArray<RemoteViews> mCachedRemoteViews = new SparseArray<>();
+    private final RunnableList mOnTerminateCallback = new RunnableList();
 
     public static LauncherAppState getInstance(final Context context) {
         return INSTANCE.get(context);
@@ -100,6 +93,8 @@
         Log.v(Launcher.TAG, "LauncherAppState initiated");
         Preconditions.assertUIThread();
 
+        mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
+                () -> context.getPackageManager().isSafeMode());
         mInvariantDeviceProfile.addOnChangeListener(modelPropertiesChanged -> {
             if (modelPropertiesChanged) {
                 refreshAndReloadLauncher();
@@ -234,6 +229,10 @@
         return mInvariantDeviceProfile;
     }
 
+    public boolean isSafeModeEnabled() {
+        return mIsSafeModeEnabled;
+    }
+
     /**
      * Shorthand for {@link #getInvariantDeviceProfile()}
      */
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index 51ba5c6..067d150 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -303,15 +303,11 @@
         const val SHOULD_SHOW_SMARTSPACE_KEY = "SHOULD_SHOW_SMARTSPACE_KEY"
         @JvmField
         val ICON_STATE =
-            nonRestorableItem(
-                    "pref_icon_shape_path",
-                "",
-                EncryptionType.MOVE_TO_DEVICE_PROTECTED
-            )
+            nonRestorableItem("pref_icon_shape_path", "", EncryptionType.MOVE_TO_DEVICE_PROTECTED)
         @JvmField
         val ALL_APPS_OVERVIEW_THRESHOLD =
             nonRestorableItem(
-                    "pref_all_apps_overview_threshold",
+                "pref_all_apps_overview_threshold",
                 180,
                 EncryptionType.MOVE_TO_DEVICE_PROTECTED
             )
@@ -319,58 +315,54 @@
         val LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE =
             nonRestorableItem(
                 "pref_long_press_nav_handle_slop_percentage",
-                        LPNH_SLOP_PERCENTAGE.get(),
-                        EncryptionType.MOVE_TO_DEVICE_PROTECTED
+                LPNH_SLOP_PERCENTAGE.get(),
+                EncryptionType.MOVE_TO_DEVICE_PROTECTED
             )
         @JvmField
         val LONG_PRESS_NAV_HANDLE_TIMEOUT_MS =
-                nonRestorableItem(
-                        "pref_long_press_nav_handle_timeout_ms",
-                        LPNH_TIMEOUT_MS.get(),
-                        EncryptionType.MOVE_TO_DEVICE_PROTECTED
-                )
+            nonRestorableItem(
+                "pref_long_press_nav_handle_timeout_ms",
+                LPNH_TIMEOUT_MS.get(),
+                EncryptionType.MOVE_TO_DEVICE_PROTECTED
+            )
         @JvmField
         val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_START_SCALE_PERCENT =
-                nonRestorableItem(
-                        "pref_long_press_nav_handle_haptic_hint_start_scale_percent",
-                        LPNH_HAPTIC_HINT_START_SCALE_PERCENT.get(),
-                        EncryptionType.MOVE_TO_DEVICE_PROTECTED
-                )
+            nonRestorableItem(
+                "pref_long_press_nav_handle_haptic_hint_start_scale_percent",
+                LPNH_HAPTIC_HINT_START_SCALE_PERCENT.get(),
+                EncryptionType.MOVE_TO_DEVICE_PROTECTED
+            )
         @JvmField
         val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_END_SCALE_PERCENT =
-                nonRestorableItem(
-                        "pref_long_press_nav_handle_haptic_hint_end_scale_percent",
-                        LPNH_HAPTIC_HINT_END_SCALE_PERCENT.get(),
-                        EncryptionType.MOVE_TO_DEVICE_PROTECTED
-                )
+            nonRestorableItem(
+                "pref_long_press_nav_handle_haptic_hint_end_scale_percent",
+                LPNH_HAPTIC_HINT_END_SCALE_PERCENT.get(),
+                EncryptionType.MOVE_TO_DEVICE_PROTECTED
+            )
         @JvmField
         val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_SCALE_EXPONENT =
-                nonRestorableItem(
-                        "pref_long_press_nav_handle_haptic_hint_scale_exponent",
-                        LPNH_HAPTIC_HINT_SCALE_EXPONENT.get(),
-                        EncryptionType.MOVE_TO_DEVICE_PROTECTED
-                )
+            nonRestorableItem(
+                "pref_long_press_nav_handle_haptic_hint_scale_exponent",
+                LPNH_HAPTIC_HINT_SCALE_EXPONENT.get(),
+                EncryptionType.MOVE_TO_DEVICE_PROTECTED
+            )
         @JvmField
         val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_ITERATIONS =
-                nonRestorableItem(
-                        "pref_long_press_nav_handle_haptic_hint_iterations",
-                        LPNH_HAPTIC_HINT_ITERATIONS.get(),
-                        EncryptionType.MOVE_TO_DEVICE_PROTECTED
-                )
+            nonRestorableItem(
+                "pref_long_press_nav_handle_haptic_hint_iterations",
+                LPNH_HAPTIC_HINT_ITERATIONS.get(),
+                EncryptionType.MOVE_TO_DEVICE_PROTECTED
+            )
         @JvmField
         val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_DELAY =
-                nonRestorableItem(
-                        "pref_long_press_nav_handle_haptic_hint_delay",
-                        LPNH_HAPTIC_HINT_DELAY.get(),
-                        EncryptionType.MOVE_TO_DEVICE_PROTECTED
-                )
+            nonRestorableItem(
+                "pref_long_press_nav_handle_haptic_hint_delay",
+                LPNH_HAPTIC_HINT_DELAY.get(),
+                EncryptionType.MOVE_TO_DEVICE_PROTECTED
+            )
         @JvmField
         val PRIVATE_SPACE_APPS =
-                nonRestorableItem(
-                        "pref_private_space_apps",
-                        0,
-                        EncryptionType.MOVE_TO_DEVICE_PROTECTED
-                )
+            nonRestorableItem("pref_private_space_apps", 0, EncryptionType.MOVE_TO_DEVICE_PROTECTED)
         @JvmField
         val THEMED_ICONS =
             backedUpItem(Themes.KEY_THEMED_ICONS, false, EncryptionType.MOVE_TO_DEVICE_PROTECTED)
@@ -420,7 +412,7 @@
             )
         @JvmField
         val IS_FIRST_LOAD_AFTER_RESTORE =
-            backedUpItem(
+            nonRestorableItem(
                 FIRST_LOAD_AFTER_RESTORE_KEY,
                 false,
                 EncryptionType.MOVE_TO_DEVICE_PROTECTED
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index 7f1d216..4ad4c71 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -1150,14 +1150,15 @@
 
         applyAdapterSideAndBottomPaddings(grid);
 
-        MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
-        mlp.leftMargin = insets.left;
-        mlp.rightMargin = insets.right;
-        setLayoutParams(mlp);
+        // Ignore left/right insets on tablet because we are already centered in-screen.
+        if (grid.isPhone) {
+            MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
+            mlp.leftMargin = insets.left;
+            mlp.rightMargin = insets.right;
+            setLayoutParams(mlp);
+        }
 
-        if (grid.isVerticalBarLayout() && !FeatureFlags.enableResponsiveWorkspace()) {
-            setPadding(grid.workspacePadding.left, 0, grid.workspacePadding.right, 0);
-        } else {
+        if (!grid.isVerticalBarLayout() || FeatureFlags.enableResponsiveWorkspace()) {
             int topPadding = grid.allAppsPadding.top;
             if (isSearchBarFloating() && !grid.isTablet) {
                 topPadding += getResources().getDimensionPixelSize(
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 03ac9df..e2c5795 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -342,7 +342,7 @@
             });
         }
 
-        if(FeatureFlags.ENABLE_PREMIUM_HAPTICS_ALL_APPS.get() && config.userControlled
+        if (FeatureFlags.ENABLE_PREMIUM_HAPTICS_ALL_APPS.get() && config.isUserControlled()
                 && Utilities.ATLEAST_S) {
             if (toState == ALL_APPS) {
                 builder.addOnFrameListener(
@@ -367,7 +367,7 @@
 
         // need to decide depending on the release velocity
         Interpolator verticalProgressInterpolator = config.getInterpolator(ANIM_VERTICAL_PROGRESS,
-                config.userControlled ? LINEAR : DECELERATE_1_7);
+                config.isUserControlled() ? LINEAR : DECELERATE_1_7);
         Animator anim = createSpringAnimation(mProgress, targetProgress);
         anim.setInterpolator(verticalProgressInterpolator);
         builder.add(anim);
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 1782791..35c07c3 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 import androidx.recyclerview.widget.DiffUtil;
 
 import com.android.launcher3.Flags;
@@ -321,6 +322,8 @@
                     break;
                 case PrivateProfileManager.STATE_ENABLED:
                     // Add PS Apps only in Enabled State.
+                    mPrivateProviderManager.addPrivateSpaceInstallAppButton(mAdapterItems);
+                    position++;
                     addAppsWithSections(mPrivateApps, position);
                     if (mActivityContext.getAppsView() != null) {
                         mActivityContext.getAppsView().getActiveRecyclerView()
@@ -338,26 +341,14 @@
             hasPrivateApps = appList.stream().
                     allMatch(mPrivateProviderManager.getItemInfoMatcher());
         }
-        int privateAppCount = 0;
-        int numberOfColumns = mActivityContext.getDeviceProfile().numShownAllAppsColumns;
-        int numberOfAppRows = (int) Math.ceil((double) appList.size() / numberOfColumns);
-        for (AppInfo info : appList) {
+        for (int i = 0; i < appList.size(); i++) {
+            AppInfo info = appList.get(i);
             // Apply decorator to private apps.
             if (hasPrivateApps) {
-                int roundRegion = ROUND_NOTHING;
-                if ((privateAppCount / numberOfColumns) == numberOfAppRows - 1) {
-                    if ((privateAppCount % numberOfColumns) == 0) {
-                        // App is the first column
-                        roundRegion = ROUND_BOTTOM_LEFT;
-                    } else if ((privateAppCount % numberOfColumns) == numberOfColumns-1) {
-                        roundRegion = ROUND_BOTTOM_RIGHT;
-                    }
-                }
                 mAdapterItems.add(AdapterItem.asAppWithDecorationInfo(info,
                         new SectionDecorationInfo(mActivityContext.getApplicationContext(),
-                                roundRegion,
+                                getRoundRegions(i, appList.size()),
                                 true /* decorateTogether */)));
-                privateAppCount += 1;
             } else {
                 mAdapterItems.add(AdapterItem.asApp(info));
             }
@@ -372,6 +363,43 @@
         }
     }
 
+    /**
+     * Determines the corner regions that should be rounded for a specific app icon based on its
+     * position in a grid. Apps that should only be cared about rounding are the apps in the last
+     * row. In the last row on the first column, the app should only be rounded on the bottom left.
+     * Apps in the middle would not be rounded and the last app on the last row will ALWAYS have a
+     * {@link SectionDecorationInfo#ROUND_BOTTOM_RIGHT}.
+     *
+     * @param appIndex The index of the app icon within the app list.
+     * @param appListSize The total number of apps within the app list.
+     * @return  An integer representing the corner regions to be rounded, using bitwise flags:
+     *          - {@link SectionDecorationInfo#ROUND_NOTHING}: No corners should be rounded.
+     *          - {@link SectionDecorationInfo#ROUND_TOP_LEFT}: Round the top-left corner.
+     *          - {@link SectionDecorationInfo#ROUND_TOP_RIGHT}: Round the top-right corner.
+     *          - {@link SectionDecorationInfo#ROUND_BOTTOM_LEFT}: Round the bottom-left corner.
+     *          - {@link SectionDecorationInfo#ROUND_BOTTOM_RIGHT}: Round the bottom-right corner.
+     */
+    @VisibleForTesting
+    int getRoundRegions(int appIndex, int appListSize) {
+        int numberOfAppRows = (int) Math.ceil((double) appListSize / mNumAppsPerRowAllApps);
+        int roundRegion = ROUND_NOTHING;
+        // App is in the last row.
+        if ((appIndex / mNumAppsPerRowAllApps) == numberOfAppRows - 1) {
+            if ((appIndex % mNumAppsPerRowAllApps) == 0) {
+                // App is the first column.
+                roundRegion = ROUND_BOTTOM_LEFT;
+            } else if ((appIndex % mNumAppsPerRowAllApps) == mNumAppsPerRowAllApps-1) {
+                // App is in the last column.
+                roundRegion = ROUND_BOTTOM_RIGHT;
+            }
+            // Ensure the last private app is rounded on the bottom right.
+            if (appIndex == appListSize - 1) {
+                roundRegion |= ROUND_BOTTOM_RIGHT;
+            }
+        }
+        return roundRegion;
+    }
+
     private static class MyDiffCallback extends DiffUtil.Callback {
 
         private final List<AdapterItem> mOldList;
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index 1ba5f8e..a1f6ebe 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -225,10 +225,10 @@
         for (FloatingHeaderRow row : mAllRows) {
             row.setup(this, mAllRows, tabsHidden);
         }
-        updateExpectedHeight();
 
         mTabsHidden = tabsHidden;
         maybeSetTabVisibility(VISIBLE);
+        updateExpectedHeight();
         mMainRV = mainRV;
         mWorkRV = workRV;
         mSearchRV = searchRV;
diff --git a/src/com/android/launcher3/allapps/PrivateProfileManager.java b/src/com/android/launcher3/allapps/PrivateProfileManager.java
index 693681b..c99b69f 100644
--- a/src/com/android/launcher3/allapps/PrivateProfileManager.java
+++ b/src/com/android/launcher3/allapps/PrivateProfileManager.java
@@ -17,11 +17,14 @@
 package com.android.launcher3.allapps;
 
 import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.MAIN;
+import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_ICON;
 import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_PRIVATE_SPACE_HEADER;
+import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_NOTHING;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.SettingsCache.PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI;
 
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
@@ -30,13 +33,21 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.launcher3.BuildConfig;
 import com.android.launcher3.Flags;
+import com.android.launcher3.R;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.uioverrides.ApiWrapper;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.SettingsCache;
+import com.android.launcher3.util.UserIconInfo;
 
 import java.util.ArrayList;
+import java.util.List;
 import java.util.function.Predicate;
 
 /**
@@ -71,6 +82,39 @@
         return adapterItems.size();
     }
 
+    /** Adds Private Space install app button to the layout. */
+    public void addPrivateSpaceInstallAppButton(List<BaseAllAppsAdapter.AdapterItem> adapterItems) {
+        Context context = mAllApps.getContext();
+        // Prepare intent
+        UserCache userCache = UserCache.getInstance(context);
+        UserHandle userHandle = userCache.getUserProfiles().stream()
+                .filter(user -> userCache.getUserInfo(user).type == UserIconInfo.TYPE_PRIVATE)
+                .findFirst()
+                .orElse(null);
+        Intent intent = ApiWrapper.getAppMarketActivityIntent(context,
+                BuildConfig.APPLICATION_ID, userHandle);
+
+        // Prepare bitmapInfo
+        Intent.ShortcutIconResource shortcut = Intent.ShortcutIconResource.fromContext(
+                context, com.android.launcher3.R.drawable.private_space_install_app_icon);
+        BitmapInfo bitmapInfo = LauncherIcons.obtain(context).createIconBitmap(shortcut);
+
+        AppInfo itemInfo = new AppInfo();
+        itemInfo.title = context.getResources().getString(R.string.ps_add_button_label);
+        itemInfo.intent = intent;
+        itemInfo.bitmap = bitmapInfo;
+        itemInfo.contentDescription = context.getResources().getString(
+                com.android.launcher3.R.string.ps_add_button_content_description);
+
+        BaseAllAppsAdapter.AdapterItem item = new BaseAllAppsAdapter.AdapterItem(VIEW_TYPE_ICON);
+        item.itemInfo = itemInfo;
+        item.decorationInfo = new SectionDecorationInfo(context, ROUND_NOTHING,
+                /* decorateTogether */ true);
+
+        adapterItems.add(item);
+        mAllApps.mAH.get(MAIN).mAdapter.notifyItemInserted(adapterItems.size() - 1);
+    }
+
     /** Disables quiet mode for Private Space User Profile. */
     public void unlockPrivateProfile() {
         enableQuietMode(false);
diff --git a/src/com/android/launcher3/allapps/SectionDecorationInfo.java b/src/com/android/launcher3/allapps/SectionDecorationInfo.java
index 1fed2b6..c438d19 100644
--- a/src/com/android/launcher3/allapps/SectionDecorationInfo.java
+++ b/src/com/android/launcher3/allapps/SectionDecorationInfo.java
@@ -22,11 +22,11 @@
 
 public class SectionDecorationInfo {
 
-    public static final int ROUND_NOTHING = 1 << 1;
-    public static final int ROUND_TOP_LEFT = 1 << 2;
-    public static final int ROUND_TOP_RIGHT = 1 << 3;
-    public static final int ROUND_BOTTOM_LEFT = 1 << 4;
-    public static final int ROUND_BOTTOM_RIGHT = 1 << 5;
+    public static final int ROUND_NOTHING = 0;
+    public static final int ROUND_TOP_LEFT = 1 << 1;
+    public static final int ROUND_TOP_RIGHT = 1 << 2;
+    public static final int ROUND_BOTTOM_LEFT = 1 << 3;
+    public static final int ROUND_BOTTOM_RIGHT = 1 << 4;
     public static final int DECORATOR_ALPHA = 255;
 
     protected boolean mShouldDecorateItemsTogether;
diff --git a/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt b/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
index 2945979..b2497a3 100644
--- a/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
+++ b/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
@@ -21,9 +21,14 @@
 import android.graphics.Rect
 import android.graphics.drawable.Drawable
 import android.util.AttributeSet
+import android.util.Log
 import android.view.Gravity
 import android.widget.FrameLayout
 import com.android.launcher3.DeviceProfile
+import com.android.launcher3.icons.BitmapInfo
+import com.android.launcher3.icons.PlaceHolderIconDrawable
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.util.Themes
 
 /**
  * A FrameLayout marking the area on an [AppPairIcon] where the visual icon will be drawn. One of
@@ -31,6 +36,8 @@
  */
 class AppPairIconGraphic @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
     FrameLayout(context, attrs) {
+    private val TAG = "AppPairIconGraphic"
+
     companion object {
         // Design specs -- the below ratios are in relation to the size of a standard app icon.
         private const val OUTER_PADDING_SCALE = 1 / 30f
@@ -61,8 +68,8 @@
 
     private lateinit var parentIcon: AppPairIcon
     private lateinit var appPairBackground: Drawable
-    private lateinit var appIcon1: Drawable
-    private lateinit var appIcon2: Drawable
+    private var appIcon1: Drawable? = null
+    private var appIcon2: Drawable? = null
 
     fun init(grid: DeviceProfile, icon: AppPairIcon) {
         // Calculate device-specific measurements
@@ -79,12 +86,33 @@
 
         appPairBackground = AppPairIconBackground(context, this)
         appPairBackground.setBounds(0, 0, backgroundSize.toInt(), backgroundSize.toInt())
-        appIcon1 = parentIcon.info.contents[0].newIcon(context)
-        appIcon2 = parentIcon.info.contents[1].newIcon(context)
-        appIcon1.setBounds(0, 0, memberIconSize.toInt(), memberIconSize.toInt())
-        appIcon2.setBounds(0, 0, memberIconSize.toInt(), memberIconSize.toInt())
+        applyIcons(parentIcon.info.contents)
     }
 
+    /** Sets up app pair member icons for drawing. */
+    private fun applyIcons(contents: ArrayList<WorkspaceItemInfo>) {
+        // App pair should always contain 2 members; if not 2, return to avoid a crash loop
+        if (contents.size != 2) {
+            Log.w(TAG, "AppPair contents not 2, size: " + contents.size, Throwable())
+            return
+        }
+
+        // Generate new icons, using themed flag if needed
+        val flags = if (Themes.isThemedIconEnabled(context)) BitmapInfo.FLAG_THEMED else 0
+        val newIcon1 = parentIcon.info.contents[0].newIcon(context, flags)
+        val newIcon2 = parentIcon.info.contents[1].newIcon(context, flags)
+
+        // If app icons did not draw fully last time, animate to full icon
+        (appIcon1 as? PlaceHolderIconDrawable)?.animateIconUpdate(newIcon1)
+        (appIcon2 as? PlaceHolderIconDrawable)?.animateIconUpdate(newIcon2)
+
+        appIcon1 = newIcon1
+        appIcon2 = newIcon2
+        appIcon1?.setBounds(0, 0, memberIconSize.toInt(), memberIconSize.toInt())
+        appIcon2?.setBounds(0, 0, memberIconSize.toInt(), memberIconSize.toInt())
+    }
+
+
     /** Gets this icon graphic's bounds, with respect to the parent icon's coordinate system. */
     fun getIconBounds(outBounds: Rect) {
         outBounds.set(0, 0, backgroundSize.toInt(), backgroundSize.toInt())
@@ -110,6 +138,16 @@
         // Draw background
         appPairBackground.draw(canvas)
 
+        // Make sure icons are loaded
+        if (
+            appIcon1 == null ||
+                appIcon2 == null ||
+                appIcon1 is PlaceHolderIconDrawable ||
+                appIcon2 is PlaceHolderIconDrawable
+        ) {
+            applyIcons(parentIcon.info.contents)
+        }
+
         // Draw first icon
         canvas.save()
         // The app icons are placed differently depending on device orientation.
@@ -118,7 +156,7 @@
         } else {
             canvas.translate(width / 2f - memberIconSize / 2f, innerPadding)
         }
-        appIcon1.draw(canvas)
+        appIcon1?.draw(canvas)
         canvas.restore()
 
         // Draw second icon
@@ -135,7 +173,7 @@
                 height - (innerPadding + memberIconSize)
             )
         }
-        appIcon2.draw(canvas)
+        appIcon2?.draw(canvas)
         canvas.restore()
     }
 }
diff --git a/src/com/android/launcher3/backuprestore/LauncherRestoreEventLogger.kt b/src/com/android/launcher3/backuprestore/LauncherRestoreEventLogger.kt
index bdac05d..063dad1 100644
--- a/src/com/android/launcher3/backuprestore/LauncherRestoreEventLogger.kt
+++ b/src/com/android/launcher3/backuprestore/LauncherRestoreEventLogger.kt
@@ -22,6 +22,8 @@
         const val RESTORE_ERROR_SHORTCUT_NOT_FOUND = "shortcut_not_found"
         const val RESTORE_ERROR_APP_NOT_INSTALLED = "app_not_installed"
         const val RESTORE_ERROR_WIDGETS_DISABLED = "widgets_disabled"
+        const val RESTORE_ERROR_PROFILE_NOT_RESTORED = "profile_not_restored"
+        const val RESTORE_ERROR_WIDGET_REMOVED = "widget_not_found"
 
         fun newInstance(context: Context?): LauncherRestoreEventLogger {
             return ResourceBasedOverride.Overrides.getObject(
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index f9d282c..61e853e 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -167,6 +167,8 @@
             "Enable the ability to generate monochromatic icons, if it is not provided by the app");
 
     // TODO(Block 8): Clean up flags
+    public static final BooleanFlag ENABLE_LAUNCHER_BR_METRICS = getDebugFlag(305984208,
+            "ENABLE_LAUNCHER_BR_METRICS", TEAMFOOD, "Enable metrics for Launcher restore");
 
     // TODO(Block 9): Clean up flags
     public static final BooleanFlag MULTI_SELECT_EDIT_MODE = getDebugFlag(270709220,
@@ -305,7 +307,7 @@
 
     // TODO(Block 17): Clean up flags
     // Aconfig migration complete for ENABLE_TASKBAR_PINNING.
-    private static final BooleanFlag ENABLE_TASKBAR_PINNING = getDebugFlag(270396583,
+    private static final BooleanFlag ENABLE_TASKBAR_PINNING = getDebugFlag(296231746,
             "ENABLE_TASKBAR_PINNING", TEAMFOOD,
             "Enables taskbar pinning to allow user to switch between transient and persistent "
                     + "taskbar flavors");
@@ -387,10 +389,6 @@
             "ENABLE_NEW_MIGRATION_LOGIC", ENABLED,
             "Enable the new grid migration logic, keeping pages when src < dest");
 
-    public static final BooleanFlag ENABLE_CACHED_WIDGET = getDebugFlag(270395008,
-            "ENABLE_CACHED_WIDGET", ENABLED,
-            "Show previously cached widgets as opposed to deferred widget where available");
-
     // TODO(Block 25): Clean up flags
     public static final BooleanFlag ENABLE_NEW_GESTURE_NAV_TUTORIAL = getDebugFlag(270396257,
             "ENABLE_NEW_GESTURE_NAV_TUTORIAL", ENABLED,
@@ -476,10 +474,10 @@
 
     // TODO(Block 33): Clean up flags
     public static final BooleanFlag ENABLE_ALL_APPS_RV_PREINFLATION = getDebugFlag(288161355,
-            "ENABLE_ALL_APPS_RV_PREINFLATION", TEAMFOOD,
+            "ENABLE_ALL_APPS_RV_PREINFLATION", ENABLED,
             "Enables preinflating all apps icons to avoid scrolling jank.");
     public static final BooleanFlag ALL_APPS_GONE_VISIBILITY = getDebugFlag(291651514,
-            "ALL_APPS_GONE_VISIBILITY", TEAMFOOD,
+            "ALL_APPS_GONE_VISIBILITY", ENABLED,
             "Set all apps container view's hidden visibility to GONE instead of INVISIBLE.");
 
     // TODO(Block 34): Empty block
diff --git a/src/com/android/launcher3/folder/PreviewItemManager.java b/src/com/android/launcher3/folder/PreviewItemManager.java
index b39e968e..871643e 100644
--- a/src/com/android/launcher3/folder/PreviewItemManager.java
+++ b/src/com/android/launcher3/folder/PreviewItemManager.java
@@ -37,6 +37,7 @@
 import android.view.View;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.Utilities;
@@ -71,7 +72,8 @@
 
     private final Context mContext;
     private final FolderIcon mIcon;
-    private final int mIconSize;
+    @VisibleForTesting
+    public final int mIconSize;
 
     // These variables are all associated with the drawing of the preview; they are stored
     // as member variables for shared usage and to avoid computation on each frame
@@ -428,7 +430,8 @@
         p.anim = anim;
     }
 
-    private void setDrawable(PreviewItemDrawingParams p, WorkspaceItemInfo item) {
+    @VisibleForTesting
+    public void setDrawable(PreviewItemDrawingParams p, WorkspaceItemInfo item) {
         if (item.hasPromiseIconUi() || (item.runtimeStatusFlags
                     & ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) != 0) {
             PreloadIconDrawable drawable = newPendingIcon(mContext, item);
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index d8388c2..6651fa0 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -117,6 +117,9 @@
         @UiEvent(doc = "Task launched from overview using SWIPE DOWN")
         LAUNCHER_TASK_LAUNCH_SWIPE_DOWN(340),
 
+        @UiEvent(doc = "App launched by dragging and dropping, probably from taskbar")
+        LAUNCHER_APP_LAUNCH_DRAGDROP(1552),
+
         @UiEvent(doc = "TASK dismissed from overview using SWIPE UP")
         LAUNCHER_TASK_DISMISS_SWIPE_UP(341),
 
diff --git a/src/com/android/launcher3/model/AllAppsList.java b/src/com/android/launcher3/model/AllAppsList.java
index 190eb78..7cbfc37 100644
--- a/src/com/android/launcher3/model/AllAppsList.java
+++ b/src/com/android/launcher3/model/AllAppsList.java
@@ -205,6 +205,8 @@
                             && installInfo.state == PackageInstallInfo.STATUS_INSTALLING) {
                         continue;
                     }
+                    // TODO(b/302115555): Handle the case when archived apps are to be updated
+                    //  during unarchival start.
                     appInfo.setProgressLevel(installInfo);
 
                     updatedAppInfos.add(appInfo);
diff --git a/src/com/android/launcher3/model/GridSizeMigrationUtil.java b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
index efd5574..af66431 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationUtil.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
@@ -262,7 +262,8 @@
             String srcTableName, String destTableName) {
         int id = copyEntryAndUpdate(helper, entry, srcTableName, destTableName);
 
-        if (entry.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
+        if (entry.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER
+                || entry.itemType == LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR) {
             for (Set<Integer> itemIds : entry.mFolderItems.values()) {
                 for (int itemId : itemIds) {
                     copyEntryAndUpdate(helper, itemId, id, srcTableName, destTableName);
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 8dc2ab3..7b9f6fa 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -17,6 +17,8 @@
 package com.android.launcher3.model;
 
 import static com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN;
+import static com.android.launcher3.Flags.enableSupportForArchiving;
+import static com.android.launcher3.Flags.enableLauncherBrMetrics;
 import static com.android.launcher3.LauncherPrefs.IS_FIRST_LOAD_AFTER_RESTORE;
 import static com.android.launcher3.LauncherPrefs.SHOULD_SHOW_SMARTSPACE;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
@@ -131,6 +133,7 @@
  *   - all apps icons
  *   - deep shortcuts within apps
  */
+@SuppressWarnings("NewApi")
 public class LoaderTask implements Runnable {
     private static final String TAG = "LoaderTask";
     public static final String SMARTSPACE_ON_HOME_SCREEN = "pref_smartspace_home_screen";
@@ -230,8 +233,11 @@
         LoaderMemoryLogger memoryLogger = new LoaderMemoryLogger();
         mIsRestoreFromBackup =
                 (Boolean) LauncherPrefs.get(mApp.getContext()).get(IS_FIRST_LOAD_AFTER_RESTORE);
-        LauncherRestoreEventLogger restoreEventLogger = LauncherRestoreEventLogger
-                .Companion.newInstance(mApp.getContext());
+        LauncherRestoreEventLogger restoreEventLogger = null;
+        if (enableLauncherBrMetrics()) {
+            restoreEventLogger = LauncherRestoreEventLogger.Companion
+                    .newInstance(mApp.getContext());
+        }
         try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
 
             List<ShortcutInfo> allShortcuts = new ArrayList<>();
@@ -362,9 +368,11 @@
             transaction.commit();
             memoryLogger.clearLogs();
             if (mIsRestoreFromBackup) {
-                restoreEventLogger.reportLauncherRestoreResults();
                 mIsRestoreFromBackup = false;
                 LauncherPrefs.get(mApp.getContext()).putSync(IS_FIRST_LOAD_AFTER_RESTORE.to(false));
+                if (restoreEventLogger != null) {
+                    restoreEventLogger.reportLauncherRestoreResults();
+                }
             }
         } catch (CancellationException e) {
             // Loader stopped, ignore
@@ -419,7 +427,7 @@
         final WidgetManagerHelper widgetHelper = new WidgetManagerHelper(context);
 
         ModelDbController dbController = mApp.getModel().getModelDbController();
-        dbController.tryMigrateDB();
+        dbController.tryMigrateDB(restoreEventLogger);
         Log.d(TAG, "loadWorkspace: loading default favorites");
         dbController.loadDefaultFavoritesIfNecessary();
 
@@ -772,13 +780,21 @@
                                     PackageInstallInfo.STATUS_INSTALLED_DOWNLOADING);
                         }
 
-                        if (c.restoreFlag != 0 && !TextUtils.isEmpty(targetPkg)) {
+                        if ((c.restoreFlag != 0
+                                || (enableSupportForArchiving()
+                                && activityInfo != null
+                                && activityInfo.getApplicationInfo().isArchived))
+                                && !TextUtils.isEmpty(targetPkg)) {
                             tempPackageKey.update(targetPkg, c.user);
                             SessionInfo si = installingPkgs.get(tempPackageKey);
                             if (si == null) {
                                 info.runtimeStatusFlags
                                         &= ~ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE;
-                            } else if (activityInfo == null) {
+                            } else if (activityInfo == null
+                                    // For archived apps, include progress info in case there is
+                                    // a pending install session post restart of device.
+                                    || (enableSupportForArchiving()
+                                    && activityInfo.getApplicationInfo().isArchived)) {
                                 int installProgress = (int) (si.getProgress() * 100);
 
                                 info.setProgressLevel(installProgress,
@@ -1095,6 +1111,8 @@
             for (int i = 0; i < apps.size(); i++) {
                 LauncherActivityInfo app = apps.get(i);
                 AppInfo appInfo = new AppInfo(app, user, quietMode);
+                // TODO(b/302115555): Handle the case when archived apps with active sessions are
+                //  loaded.
 
                 iconRequestInfos.add(new IconRequestInfo<>(
                         appInfo, app, /* useLowResIcon= */ false));
diff --git a/src/com/android/launcher3/model/ModelDbController.java b/src/com/android/launcher3/model/ModelDbController.java
index d2b7161..c68274a 100644
--- a/src/com/android/launcher3/model/ModelDbController.java
+++ b/src/com/android/launcher3/model/ModelDbController.java
@@ -19,6 +19,7 @@
 import static android.util.Base64.NO_WRAP;
 
 import static com.android.launcher3.DefaultLayoutParser.RES_PARTNER_DEFAULT_LAYOUT;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE;
 import static com.android.launcher3.LauncherSettings.Favorites.addTableToDb;
 import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_KEY;
 import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_LABEL;
@@ -48,6 +49,7 @@
 import android.util.Log;
 import android.util.Xml;
 
+import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
 
 import com.android.launcher3.AutoInstallsLayout;
@@ -62,6 +64,7 @@
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.backuprestore.LauncherRestoreEventLogger;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.provider.LauncherDbUtils;
@@ -86,6 +89,7 @@
     private static final String TAG = "LauncherProvider";
 
     private static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED";
+    private static final String RESTORE_ERROR_GRID_MIGRATION_FAILURE = "grid_migration_failed";
     public static final String EXTRA_DB_NAME = "db_name";
 
     protected DatabaseHelper mOpenHelper;
@@ -261,8 +265,12 @@
     /**
      * Migrates the DB if needed. If the migration failed, it clears the DB.
      */
-    public void tryMigrateDB() {
+    public void tryMigrateDB(@Nullable LauncherRestoreEventLogger restoreEventLogger) {
+
         if (!migrateGridIfNeeded()) {
+            if (restoreEventLogger != null) {
+                sendMetricsForFailedMigration(restoreEventLogger, getDb());
+            }
             FileLog.d(TAG, "Migration failed: resetting launcher database");
             createEmptyDB();
             LauncherPrefs.get(mContext).putSync(
@@ -313,6 +321,30 @@
     }
 
     /**
+     * In case of migration failure, report metrics for the count of each itemType in the DB.
+     * @param restoreEventLogger logger used to report Launcher restore metrics
+     */
+    private void sendMetricsForFailedMigration(LauncherRestoreEventLogger restoreEventLogger,
+            SQLiteDatabase db) {
+        try (Cursor cursor = db.rawQuery(
+                "SELECT itemType, COUNT(*) AS count FROM favorites GROUP BY itemType",
+                null
+        )) {
+            if (cursor.moveToFirst()) {
+                do {
+                    restoreEventLogger.logFavoritesItemsRestoreFailed(
+                            cursor.getInt(cursor.getColumnIndexOrThrow(ITEM_TYPE)),
+                            cursor.getInt(cursor.getColumnIndexOrThrow("count")),
+                            RESTORE_ERROR_GRID_MIGRATION_FAILURE
+                    );
+                } while (cursor.moveToNext());
+            }
+        } catch (Exception e) {
+            FileLog.e(TAG, "sendMetricsForFailedDb: Error reading from database", e);
+        }
+    }
+
+    /**
      * Returns the underlying model database
      */
     public SQLiteDatabase getDb() {
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 4f2d398..069e96b 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -15,9 +15,11 @@
  */
 package com.android.launcher3.model;
 
+import static com.android.launcher3.Flags.enableSupportForArchiving;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_WORK_PROFILE_QUIET_MODE_ENABLED;
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED;
 import static com.android.launcher3.model.data.WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON;
 import static com.android.launcher3.model.data.WorkspaceItemInfo.FLAG_RESTORED_ICON;
 
@@ -67,6 +69,7 @@
  * Handles updates due to changes in package manager (app installed/updated/removed)
  * or when a user availability changes.
  */
+@SuppressWarnings("NewApi")
 public class PackageUpdatedTask extends BaseModelUpdateTask {
 
     // TODO(b/290090023): Set to false after root causing is done.
@@ -269,6 +272,16 @@
                                             : PackageManagerHelper.getLoadingProgress(
                                                     activities.get(0)),
                                     PackageInstallInfo.STATUS_INSTALLED_DOWNLOADING);
+                            // In case an app is archived, we need to make sure that archived state
+                            // in WorkspaceItemInfo is refreshed.
+                            if (enableSupportForArchiving() && !activities.isEmpty()) {
+                                boolean newArchivalState = activities.get(
+                                        0).getActivityInfo().isArchived;
+                                if (newArchivalState != si.isArchived()) {
+                                    si.runtimeStatusFlags ^= FLAG_ARCHIVED;
+                                    infoUpdated = true;
+                                }
+                            }
                             if (si.itemType == Favorites.ITEM_TYPE_APPLICATION) {
                                 iconCache.getTitleAndIcon(si, si.usingLowResIcon());
                                 infoUpdated = true;
diff --git a/src/com/android/launcher3/model/data/AppInfo.java b/src/com/android/launcher3/model/data/AppInfo.java
index 6c2f589..ea8a7a1 100644
--- a/src/com/android/launcher3/model/data/AppInfo.java
+++ b/src/com/android/launcher3/model/data/AppInfo.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.model.data;
 
+import static com.android.launcher3.Flags.enableSupportForArchiving;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APPS;
 
 import android.content.ComponentName;
@@ -40,6 +41,7 @@
 /**
  * Represents an app in AllAppsView.
  */
+@SuppressWarnings("NewApi")
 public class AppInfo extends ItemInfoWithIcon implements WorkspaceItemFactory {
 
     public static final AppInfo[] EMPTY_ARRAY = new AppInfo[0];
@@ -53,7 +55,8 @@
      */
     public Intent intent;
 
-    @NonNull
+    // componentName for the Private Space Install App button can be null
+    @Nullable
     public ComponentName componentName;
 
     // Section name used for indexing.
@@ -172,6 +175,9 @@
         if (PackageManagerHelper.isAppSuspended(appInfo)) {
             info.runtimeStatusFlags |= FLAG_DISABLED_SUSPENDED;
         }
+        if (enableSupportForArchiving() && lai.getActivityInfo().isArchived) {
+            info.runtimeStatusFlags |= FLAG_ARCHIVED;
+        }
         info.runtimeStatusFlags |= (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0
                 ? FLAG_SYSTEM_NO : FLAG_SYSTEM_YES;
 
diff --git a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
index dc180d8..58b12b1 100644
--- a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
@@ -16,8 +16,11 @@
 
 package com.android.launcher3.model.data;
 
+import static com.android.launcher3.Flags.enableSupportForArchiving;
+
 import android.content.Context;
 import android.content.Intent;
+import android.os.Process;
 
 import androidx.annotation.Nullable;
 
@@ -26,7 +29,7 @@
 import com.android.launcher3.icons.FastBitmapDrawable;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.pm.PackageInstallInfo;
-import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.uioverrides.ApiWrapper;
 
 /**
  * Represents an ItemInfo which also holds an icon.
@@ -113,6 +116,12 @@
     public static final int FLAG_NOT_PINNABLE = 1 << 13;
 
     /**
+     * Flag indicating whether the package related to the item & user corresponds to that of
+     * archived app.
+     */
+    public static final int FLAG_ARCHIVED = 1 << 14;
+
+    /**
      * Status associated with the system state of the underlying item. This is calculated every
      * time a new info is created and not persisted on the disk.
      */
@@ -142,6 +151,15 @@
     }
 
     /**
+     * Returns true if the app corresponding to the item is archived. */
+    public boolean isArchived() {
+        if (!enableSupportForArchiving()) {
+            return false;
+        }
+        return (runtimeStatusFlags & FLAG_ARCHIVED) != 0;
+    }
+
+    /**
      * Indicates whether we're using a low res icon
      */
     public boolean usingLowResIcon() {
@@ -157,7 +175,7 @@
     public boolean isAppStartable() {
         return ((runtimeStatusFlags & FLAG_INSTALL_SESSION_ACTIVE) == 0)
                 && (((runtimeStatusFlags & FLAG_INCREMENTAL_DOWNLOAD_ACTIVE) != 0)
-                    || mProgressLevel == 100);
+                    || mProgressLevel == 100 || isArchived());
     }
 
     /**
@@ -166,7 +184,10 @@
      * progress.
      */
     public int getProgressLevel() {
-        if ((runtimeStatusFlags & FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) != 0) {
+        if (((runtimeStatusFlags & FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) != 0)
+                // This condition for archived apps is so that in case unarchival/update of
+                // archived app is cancelled, the state transitions back to 0% installed state.
+                || isArchived()) {
             return mProgressLevel;
         }
         return 100;
@@ -216,7 +237,8 @@
         String targetPackage = getTargetPackage();
 
         return targetPackage != null
-                ? new PackageManagerHelper(context).getMarketIntent(targetPackage)
+                ? ApiWrapper.getAppMarketActivityIntent(
+                        context, targetPackage, Process.myUserHandle())
                 : null;
     }
 
diff --git a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
index 3ce194d..c67ec5a 100644
--- a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
+++ b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
@@ -148,9 +148,19 @@
 
 
     public final boolean isPromise() {
-        return hasStatusFlag(FLAG_RESTORED_ICON | FLAG_AUTOINSTALL_ICON);
+        return hasStatusFlag(FLAG_RESTORED_ICON | FLAG_AUTOINSTALL_ICON)
+                // For archived apps, promise icons are always ready to be displayed.
+                || isArchived();
     }
 
+    /**
+     * Returns true if the workspace item supports promise icon UI. There are a few cases where they
+     * are supported:
+     * 1. Icons to be restored via backup/restore.
+     * 2. Icons added as an auto-install app.
+     * 3. Icons added due to it being an active install session created by the user.
+     * 4. Icons for archived apps.
+     */
     public boolean hasPromiseIconUi() {
         return isPromise() && !hasStatusFlag(FLAG_SUPPORTS_WEB_UI);
     }
diff --git a/src/com/android/launcher3/pm/InstallSessionHelper.java b/src/com/android/launcher3/pm/InstallSessionHelper.java
index cb3c16c..ca27eb2 100644
--- a/src/com/android/launcher3/pm/InstallSessionHelper.java
+++ b/src/com/android/launcher3/pm/InstallSessionHelper.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.pm;
 
+import static com.android.launcher3.Flags.enableSupportForArchiving;
+
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.LauncherApps;
@@ -51,6 +53,7 @@
 /**
  * Utility class to tracking install sessions
  */
+@SuppressWarnings("NewApi")
 public class InstallSessionHelper {
 
     @NonNull
@@ -227,6 +230,11 @@
     }
 
     public boolean verifySessionInfo(@Nullable final PackageInstaller.SessionInfo sessionInfo) {
+        // For archived apps we always want to show promise icons and the checks below don't apply.
+        if (enableSupportForArchiving() && sessionInfo != null && sessionInfo.isUnarchival()) {
+            return true;
+        }
+
         return verify(sessionInfo) != null
                 && sessionInfo.getInstallReason() == PackageManager.INSTALL_REASON_USER
                 && sessionInfo.getAppIcon() != null
diff --git a/src/com/android/launcher3/pm/InstallSessionTracker.java b/src/com/android/launcher3/pm/InstallSessionTracker.java
index 41908d3..e4a2045 100644
--- a/src/com/android/launcher3/pm/InstallSessionTracker.java
+++ b/src/com/android/launcher3/pm/InstallSessionTracker.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.pm;
 
+import static com.android.launcher3.Flags.enableSupportForArchiving;
 import static com.android.launcher3.pm.InstallSessionHelper.getUserHandle;
 import static com.android.launcher3.pm.PackageInstallInfo.STATUS_FAILED;
 import static com.android.launcher3.pm.PackageInstallInfo.STATUS_INSTALLED;
@@ -36,6 +37,7 @@
 import java.lang.ref.WeakReference;
 import java.util.Objects;
 
+@SuppressWarnings("NewApi")
 @WorkerThread
 public class InstallSessionTracker extends PackageInstaller.SessionCallback {
 
@@ -77,6 +79,12 @@
         }
 
         helper.tryQueuePromiseAppIcon(sessionInfo);
+
+        if (enableSupportForArchiving() && sessionInfo != null && sessionInfo.isUnarchival()) {
+            // For archived apps, icon could already be present on the workspace. To make sure
+            // the icon state is updated, we send a change event.
+            callback.onPackageStateChanged(PackageInstallInfo.fromInstallingState(sessionInfo));
+        }
     }
 
     @Override
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index e3314d4..4d4a8f7 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -636,10 +636,10 @@
         return getResources().getDimensionPixelSize(R.dimen.popup_vertical_padding);
     }
 
-    protected AnimatorSet getOpenCloseAnimator(boolean isOpening, int scaleDuration,
-            int fadeStartDelay, int fadeDuration, int childFadeStartDelay, int childFadeDuration,
-            Interpolator interpolator) {
-
+    /**
+     * Sets X and Y pivots for the view animation considering arrow position.
+     */
+    protected void setPivotForOpenCloseAnimation() {
         int arrowCenter = mArrowOffsetHorizontal + mArrowWidth / 2;
         if (mIsArrowRotated) {
             setPivotX(mIsLeftAligned ? 0f : getMeasuredWidth());
@@ -648,6 +648,14 @@
             setPivotX(mIsLeftAligned ? arrowCenter : getMeasuredWidth() - arrowCenter);
             setPivotY(mIsAboveIcon ? getMeasuredHeight() : 0f);
         }
+    }
+
+
+    protected AnimatorSet getOpenCloseAnimator(boolean isOpening, int scaleDuration,
+            int fadeStartDelay, int fadeDuration, int childFadeStartDelay, int childFadeDuration,
+            Interpolator interpolator) {
+
+        setPivotForOpenCloseAnimation();
 
         float[] alphaValues = isOpening ? new float[] {0, 1} : new float[] {1, 0};
         float[] scaleValues = isOpening ? new float[] {0.5f, 1.02f} : new float[] {1f, 0.5f};
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 69bba69..f39f806 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -7,6 +7,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Rect;
+import android.os.Process;
 import android.view.View;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.ImageView;
@@ -22,6 +23,7 @@
 import com.android.launcher3.model.WidgetItem;
 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.InstantAppResolver;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.PackageUserKey;
@@ -237,8 +239,9 @@
 
         @Override
         public void onClick(View view) {
-            Intent intent = new PackageManagerHelper(view.getContext()).getMarketIntent(
-                    mItemInfo.getTargetComponent().getPackageName());
+            Intent intent = ApiWrapper.getAppMarketActivityIntent(view.getContext(),
+                    mItemInfo.getTargetComponent().getPackageName(),
+                    Process.myUserHandle());
             mTarget.startActivitySafely(view, intent, mItemInfo);
             AbstractFloatingView.closeAllOpenViews(mTarget);
         }
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index 20b2971..a969c8f 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -18,12 +18,19 @@
 
 import static android.os.Process.myUserHandle;
 
+import static com.android.launcher3.Flags.enableLauncherBrMetrics;
 import static com.android.launcher3.InvariantDeviceProfile.TYPE_MULTI_DISPLAY;
 import static com.android.launcher3.LauncherPrefs.APP_WIDGET_IDS;
 import static com.android.launcher3.LauncherPrefs.IS_FIRST_LOAD_AFTER_RESTORE;
 import static com.android.launcher3.LauncherPrefs.OLD_APP_WIDGET_IDS;
 import static com.android.launcher3.LauncherPrefs.RESTORE_DEVICE;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
+import static com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RESTORE_ERROR_PROFILE_NOT_RESTORED;
+import static com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RESTORE_ERROR_WIDGETS_DISABLED;
+import static com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RESTORE_ERROR_WIDGET_REMOVED;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_LAUNCHER_BR_METRICS;
 import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
 import static com.android.launcher3.widget.LauncherWidgetHolder.APPWIDGET_HOST_ID;
 
@@ -53,6 +60,7 @@
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.backuprestore.LauncherRestoreEventLogger;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.model.DeviceGridState;
 import com.android.launcher3.model.LoaderTask;
@@ -91,7 +99,8 @@
 
     public static final String APPWIDGET_OLD_IDS = "appwidget_old_ids";
     public static final String APPWIDGET_IDS = "appwidget_ids";
-    private static final String[] DB_COLUMNS_TO_LOG = {"profileId", "title", "itemType", "screen",
+    @VisibleForTesting
+    public static final String[] DB_COLUMNS_TO_LOG = {"profileId", "title", "itemType", "screen",
             "container", "cellX", "cellY", "spanX", "spanY", "intent", "appWidgetProvider",
             "appWidgetId", "restored"};
 
@@ -123,8 +132,11 @@
         FileLog.d(TAG, "performRestore: starting restore from db");
         try (SQLiteTransaction t = new SQLiteTransaction(db)) {
             RestoreDbTask task = new RestoreDbTask();
-            task.sanitizeDB(context, controller, db, new BackupManager(context));
-            task.restoreAppWidgetIdsIfExists(context, controller);
+            BackupManager backupManager = new BackupManager(context);
+            LauncherRestoreEventLogger restoreEventLogger =
+                    LauncherRestoreEventLogger.Companion.newInstance(context);
+            task.sanitizeDB(context, controller, db, backupManager, restoreEventLogger);
+            task.restoreAppWidgetIdsIfExists(context, controller, restoreEventLogger);
             t.commit();
             return true;
         } catch (Exception e) {
@@ -147,7 +159,8 @@
      */
     @VisibleForTesting
     protected int sanitizeDB(Context context, ModelDbController controller, SQLiteDatabase db,
-            BackupManager backupManager) throws Exception {
+            BackupManager backupManager, LauncherRestoreEventLogger restoreEventLogger)
+            throws Exception {
         logFavoritesTable(db, "Old Launcher Database before sanitizing:", null, null);
         // Primary user ids
         long myProfileId = controller.getSerialNumberForUser(myUserHandle());
@@ -186,6 +199,9 @@
         Arrays.fill(args, "?");
         final String where = "profileId NOT IN (" + TextUtils.join(", ", Arrays.asList(args)) + ")";
         logFavoritesTable(db, "items to delete from unrestored profiles:", where, profileIds);
+        if (enableLauncherBrMetrics() || ENABLE_LAUNCHER_BR_METRICS.get()) {
+            reportUnrestoredProfiles(db, where, profileIds, restoreEventLogger);
+        }
         int itemsDeletedCount = db.delete(Favorites.TABLE_NAME, where, profileIds);
         FileLog.d(TAG, itemsDeletedCount + " total items from unrestored user(s) were deleted");
 
@@ -345,21 +361,24 @@
         DeviceGridState deviceGridState = new DeviceGridState(context);
         FileLog.d(TAG, "restore initiated from backup: DeviceGridState=" + deviceGridState);
         LauncherPrefs.get(context).putSync(RESTORE_DEVICE.to(deviceGridState.getDeviceType()));
-        LauncherPrefs.get(context).putSync(IS_FIRST_LOAD_AFTER_RESTORE.to(true));
+        if (enableLauncherBrMetrics() || ENABLE_LAUNCHER_BR_METRICS.get()) {
+            LauncherPrefs.get(context).putSync(IS_FIRST_LOAD_AFTER_RESTORE.to(true));
+        }
     }
 
     @WorkerThread
     @VisibleForTesting
-    void restoreAppWidgetIdsIfExists(Context context, ModelDbController controller) {
+    void restoreAppWidgetIdsIfExists(Context context, ModelDbController controller,
+            LauncherRestoreEventLogger restoreEventLogger) {
         LauncherPrefs lp = LauncherPrefs.get(context);
         if (lp.has(APP_WIDGET_IDS, OLD_APP_WIDGET_IDS)) {
             AppWidgetHost host = new AppWidgetHost(context, APPWIDGET_HOST_ID);
-            restoreAppWidgetIds(context, controller,
+            restoreAppWidgetIds(context, controller, restoreEventLogger,
                     IntArray.fromConcatString(lp.get(OLD_APP_WIDGET_IDS)).toArray(),
                     IntArray.fromConcatString(lp.get(APP_WIDGET_IDS)).toArray(),
                     host);
         } else {
-            FileLog.d(TAG, "No app widget ids were received from backup to restore.");
+            FileLog.d(TAG, "Did not receive new app widget id map during Launcher restore");
         }
 
         lp.remove(APP_WIDGET_IDS, OLD_APP_WIDGET_IDS);
@@ -370,10 +389,13 @@
      */
     @WorkerThread
     private void restoreAppWidgetIds(Context context, ModelDbController controller,
-            int[] oldWidgetIds, int[] newWidgetIds, @NonNull AppWidgetHost host) {
+            LauncherRestoreEventLogger launcherRestoreEventLogger, int[] oldWidgetIds,
+            int[] newWidgetIds, @NonNull AppWidgetHost host) {
         if (WidgetsModel.GO_DISABLE_WIDGETS) {
             FileLog.e(TAG, "Skipping widget ID remap as widgets not supported");
             host.deleteHost();
+            launcherRestoreEventLogger.logFavoritesItemsRestoreFailed(Favorites.ITEM_TYPE_APPWIDGET,
+                    oldWidgetIds.length, RESTORE_ERROR_WIDGETS_DISABLED);
             return;
         }
         if (!RestoreDbTask.isPending(context)) {
@@ -437,11 +459,16 @@
                         FileLog.d(TAG, "Deleting widgetId: " + newWidgetIds[i] + " with old id: "
                                 + oldWidgetId);
                         host.deleteAppWidgetId(newWidgetIds[i]);
+                        launcherRestoreEventLogger.logSingleFavoritesItemRestoreFailed(
+                                ITEM_TYPE_APPWIDGET,
+                                RESTORE_ERROR_WIDGET_REMOVED
+                        );
                     }
                 }
             }
         }
 
+        logFavoritesTable(controller.getDb(), "launcher db after remap widget ids", null, null);
         LauncherAppState app = LauncherAppState.getInstanceNoCreate();
         if (app != null) {
             app.getModel().forceReload();
@@ -476,17 +503,16 @@
             StringBuilder builder = new StringBuilder();
             builder.append("[");
             for (int i = 0; i < widgetIdList.size(); i++) {
-                builder.append("[")
+                builder.append("[appWidgetId=")
                         .append(widgetIdList.get(i))
-                        .append(", ")
+                        .append(", restoreFlag=")
                         .append(widgetRestoreList.get(i))
-                        .append(", ")
+                        .append(", profileId=")
                         .append(widgetProfileIdList.get(i))
                         .append("]");
             }
             builder.append("]");
-            Log.d(TAG, "restoreAppWidgetIds: all widget ids in database: "
-                    + builder);
+            Log.d(TAG, "restoreAppWidgetIds: all widget ids in database: " + builder);
         } catch (Exception ex) {
             Log.e(TAG, "Getting widget ids from the database failed", ex);
         }
@@ -545,7 +571,7 @@
      */
     public static void logFavoritesTable(SQLiteDatabase database, @NonNull String logHeader,
             String where, String[] profileIds) {
-        try (Cursor itemsToDelete = database.query(
+        try (Cursor cursor = database.query(
                 /* table */ Favorites.TABLE_NAME,
                 /* columns */ DB_COLUMNS_TO_LOG,
                 /* selection */ where,
@@ -554,26 +580,53 @@
                 /* having */ null,
                 /* orderBy */ null
         )) {
-            if (itemsToDelete.moveToFirst()) {
-                String[] columnNames = itemsToDelete.getColumnNames();
+            if (cursor.moveToFirst()) {
+                String[] columnNames = cursor.getColumnNames();
                 StringBuilder stringBuilder = new StringBuilder(logHeader + "\n");
                 do {
                     for (String columnName : columnNames) {
                         stringBuilder.append(columnName)
                                 .append("=")
-                                .append(itemsToDelete.getString(
-                                        itemsToDelete.getColumnIndex(columnName)))
+                                .append(cursor.getString(
+                                        cursor.getColumnIndex(columnName)))
                                 .append(" ");
                     }
                     stringBuilder.append("\n");
-                } while (itemsToDelete.moveToNext());
+                } while (cursor.moveToNext());
                 FileLog.d(TAG, stringBuilder.toString());
             } else {
-                FileLog.d(TAG, "logFavoritesTable: No items found from query for"
+                FileLog.d(TAG, "logFavoritesTable: No items found from query for "
                         + "\"" + logHeader + "\"");
             }
         } catch (Exception e) {
             FileLog.e(TAG, "logFavoritesTable: Error reading from database", e);
         }
     }
+
+
+    /**
+     * Queries and reports the count of each itemType to be removed due to unrestored profiles.
+     * @param database The Launcher db to query from.
+     * @param where Query being used for to find unrestored profiles
+     * @param profileIds profile ids that were not restored
+     * @param restoreEventLogger Backup/Restore Logger to report metrics
+     */
+    private void reportUnrestoredProfiles(SQLiteDatabase database, String where,
+            String[] profileIds, LauncherRestoreEventLogger restoreEventLogger) {
+        final String query = "SELECT itemType, COUNT(*) AS count FROM favorites WHERE "
+                + where + " GROUP BY itemType";
+        try (Cursor cursor = database.rawQuery(query, profileIds)) {
+            if (cursor.moveToFirst()) {
+                do {
+                    restoreEventLogger.logFavoritesItemsRestoreFailed(
+                            cursor.getInt(cursor.getColumnIndexOrThrow(ITEM_TYPE)),
+                            cursor.getInt(cursor.getColumnIndexOrThrow("count")),
+                            RESTORE_ERROR_PROFILE_NOT_RESTORED
+                    );
+                } while (cursor.moveToNext());
+            }
+        } catch (Exception e) {
+            FileLog.e(TAG, "reportUnrestoredProfiles: Error reading from database", e);
+        }
+    }
 }
diff --git a/src/com/android/launcher3/responsive/ResponsiveSpec.kt b/src/com/android/launcher3/responsive/ResponsiveSpec.kt
index b0e1b27..65e0b32 100644
--- a/src/com/android/launcher3/responsive/ResponsiveSpec.kt
+++ b/src/com/android/launcher3/responsive/ResponsiveSpec.kt
@@ -59,7 +59,7 @@
     val cellSize: SizeSpec,
 ) : IResponsiveSpec {
     init {
-        check(isValid()) { "Invalid ResponsiveSpec found." }
+        check(isValid()) { "Invalid ResponsiveSpec found. $this" }
     }
 
     constructor(
@@ -106,7 +106,7 @@
         }
 
         if (!isValidRemainderSpace()) {
-            logError("The total Remainder Space used must be lower or equal to 100%.")
+            logError("The total Remainder Space used must be equal to 0 or 1.")
             return false
         }
 
@@ -131,11 +131,12 @@
     }
 
     private fun isValidRemainderSpace(): Boolean {
-        // TODO(b/313621277): This validation must be update do accept only 0 or 1 instead of <= 1f.
-        return startPadding.ofRemainderSpace +
-            endPadding.ofRemainderSpace +
-            gutter.ofRemainderSpace +
-            cellSize.ofRemainderSpace <= 1f
+        val remainderSpaceUsed =
+            startPadding.ofRemainderSpace +
+                endPadding.ofRemainderSpace +
+                gutter.ofRemainderSpace +
+                cellSize.ofRemainderSpace
+        return remainderSpaceUsed == 0f || remainderSpaceUsed == 1f
     }
 
     private fun isValidAvailableSpace(): Boolean {
@@ -254,8 +255,8 @@
 
         startPaddingPx = spec.startPadding.getRemainderSpaceValue(remainderSpace, startPaddingPx)
         endPaddingPx = spec.endPadding.getRemainderSpaceValue(remainderSpace, endPaddingPx)
-        gutterPx = spec.gutter.getRemainderSpaceValue(remainderSpace, gutterPx)
-        cellSizePx = spec.cellSize.getRemainderSpaceValue(remainderSpace, cellSizePx)
+        gutterPx = spec.gutter.getRemainderSpaceValue(remainderSpace, gutterPx, gutters)
+        cellSizePx = spec.cellSize.getRemainderSpaceValue(remainderSpace, cellSizePx, cells)
     }
 
     override fun hashCode(): Int {
diff --git a/src/com/android/launcher3/responsive/SizeSpec.kt b/src/com/android/launcher3/responsive/SizeSpec.kt
index d146898..41dcd5e 100644
--- a/src/com/android/launcher3/responsive/SizeSpec.kt
+++ b/src/com/android/launcher3/responsive/SizeSpec.kt
@@ -57,11 +57,16 @@
     /**
      * Calculates the [SizeSpec] value when remainder space value is defined. If no remainderSpace
      * is 0, returns a default value.
+     *
+     * @param remainderSpace The remainder space to be used for the calculation
+     * @param defaultValue The default value to be returned when no ofRemainderSpace is defined
+     * @param factor A number to divide the remainder space. The default value is 1. This property
+     *   is used to split equally the remainder space by the number of cells and gutters.
      */
-    fun getRemainderSpaceValue(remainderSpace: Int, defaultValue: Int): Int {
+    fun getRemainderSpaceValue(remainderSpace: Int, defaultValue: Int, factor: Int = 1): Int {
         val remainderSpaceValue =
             if (ofRemainderSpace > 0) {
-                (ofRemainderSpace * remainderSpace).roundToInt()
+                (ofRemainderSpace * remainderSpace / factor).roundToInt()
             } else {
                 defaultValue
             }
diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java
index 360ff7e..e8b3066 100644
--- a/src/com/android/launcher3/statemanager/StateManager.java
+++ b/src/com/android/launcher3/statemanager/StateManager.java
@@ -19,6 +19,7 @@
 import static android.animation.ValueAnimator.areAnimatorsEnabled;
 
 import static com.android.launcher3.anim.AnimatorPlaybackController.callListenerCommandRecursively;
+import static com.android.launcher3.states.StateAnimationConfig.HANDLE_STATE_APPLY;
 import static com.android.launcher3.states.StateAnimationConfig.SKIP_ALL_ANIMATIONS;
 
 import android.animation.Animator;
@@ -36,10 +37,13 @@
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.states.StateAnimationConfig.AnimationFlags;
+import com.android.launcher3.states.StateAnimationConfig.AnimationPropertyFlags;
 import com.android.launcher3.testing.shared.TestProtocol;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.stream.Collectors;
 
 /**
  * Class to manage transitions between different states for a StatefulActivity based on different
@@ -189,7 +193,7 @@
 
     public void reapplyState(boolean cancelCurrentAnimation) {
         boolean wasInAnimation = mConfig.currentAnimation != null;
-        if (cancelCurrentAnimation) {
+        if (cancelCurrentAnimation && (mConfig.animProps & HANDLE_STATE_APPLY) == 0) {
             // Animation canceling can trigger a cleanup routine, causing problems when we are in a
             // launcher state that relies on member variable data. So if we are in one of those
             // states, accelerate the current animation to its end point rather than canceling it
@@ -227,7 +231,15 @@
 
     private void goToState(
             STATE_TYPE state, boolean animated, long delay, AnimatorListener listener) {
-        Log.d(TestProtocol.OVERVIEW_OVER_HOME, "go to state " + state);
+        String stackTrace = Log.getStackTraceString(new Exception("tracing state transition"));
+        String truncatedTrace =
+                Arrays.stream(stackTrace.split("\\n"))
+                    .limit(5)
+                    .skip(1) // Removes the line "java.lang.Exception: tracing state transition"
+                    .filter(traceLine -> !traceLine.contains("StateManager.goToState"))
+                    .collect(Collectors.joining("\n"));
+        Log.d(TestProtocol.OVERVIEW_OVER_HOME,
+                "go to state " + state + " partial trace:\n" + truncatedTrace);
 
         animated &= areAnimatorsEnabled();
         if (mActivity.isInState(state)) {
@@ -237,7 +249,7 @@
                     listener.onAnimationEnd(null);
                 }
                 return;
-            } else if ((!mConfig.userControlled && animated && mConfig.targetState == state)
+            } else if ((!mConfig.isUserControlled() && animated && mConfig.targetState == state)
                     || mState.shouldPreserveDataStateOnReapply()) {
                 // We are running the same animation as requested, and/or target state should not be
                 // reset -- allow the current animation to complete instead of canceling it.
@@ -343,7 +355,7 @@
 
     public AnimatorPlaybackController createAnimationToNewWorkspace(STATE_TYPE state,
             StateAnimationConfig config) {
-        config.userControlled = true;
+        config.animProps |= StateAnimationConfig.USER_CONTROLLED;
         cancelAnimation();
         config.copyTo(mConfig);
         mConfig.playbackController = createAnimationToNewWorkspaceInternal(state)
@@ -418,7 +430,7 @@
     }
 
     public void moveToRestState(boolean isAnimated) {
-        if (mConfig.currentAnimation != null && mConfig.userControlled) {
+        if (mConfig.currentAnimation != null && mConfig.isUserControlled()) {
             // The user is doing something. Lets not mess it up
             return;
         }
@@ -450,10 +462,18 @@
         }
     }
 
+    /**
+     * Sets the provided controller as the current user controlled state animation
+     */
     public void setCurrentUserControlledAnimation(AnimatorPlaybackController controller) {
+        setCurrentAnimation(controller, StateAnimationConfig.USER_CONTROLLED);
+    }
+
+    public void setCurrentAnimation(AnimatorPlaybackController controller,
+            @AnimationPropertyFlags int animationProps) {
         clearCurrentAnimation();
         setCurrentAnimation(controller.getTarget());
-        mConfig.userControlled = true;
+        mConfig.animProps = animationProps;
         mConfig.playbackController = controller;
     }
 
diff --git a/src/com/android/launcher3/statemanager/StatefulActivity.java b/src/com/android/launcher3/statemanager/StatefulActivity.java
index 520f33c..30ba703 100644
--- a/src/com/android/launcher3/statemanager/StatefulActivity.java
+++ b/src/com/android/launcher3/statemanager/StatefulActivity.java
@@ -126,16 +126,8 @@
 
     @Override
     public void reapplyUi() {
-        reapplyUi(true /* cancelCurrentAnimation */);
-    }
-
-    /**
-     * Re-applies if any state transition is not running, optionally cancelling
-     * the transition if requested.
-     */
-    public void reapplyUi(boolean cancelCurrentAnimation) {
         getRootView().dispatchInsets();
-        getStateManager().reapplyState(cancelCurrentAnimation);
+        getStateManager().reapplyState(true /* cancelCurrentAnimation */);
     }
 
     @Override
diff --git a/src/com/android/launcher3/states/StateAnimationConfig.java b/src/com/android/launcher3/states/StateAnimationConfig.java
index 0d9e010..0ca5afd 100644
--- a/src/com/android/launcher3/states/StateAnimationConfig.java
+++ b/src/com/android/launcher3/states/StateAnimationConfig.java
@@ -40,8 +40,19 @@
     public static final int SKIP_DEPTH_CONTROLLER = 1 << 2;
     public static final int SKIP_SCRIM = 1 << 3;
 
+    @IntDef(flag = true, value = {
+            USER_CONTROLLED,
+            HANDLE_STATE_APPLY
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AnimationPropertyFlags {}
+    // Indicates that the animation is controlled by the user
+    public static final int USER_CONTROLLED = 1 << 0;
+    // Indicates that he animation can survive state UI resets due to inset or config changes
+    public static final int HANDLE_STATE_APPLY = 1 << 1;
+
     public long duration;
-    public boolean userControlled;
+    public @AnimationPropertyFlags int animProps = 0;
     public @AnimationFlags int animFlags = 0;
 
 
@@ -105,12 +116,16 @@
     public void copyTo(StateAnimationConfig target) {
         target.duration = duration;
         target.animFlags = animFlags;
-        target.userControlled = userControlled;
+        target.animProps = animProps;
         for (int i = 0; i < ANIM_TYPES_COUNT; i++) {
             target.mInterpolators[i] = mInterpolators[i];
         }
     }
 
+    public boolean isUserControlled() {
+        return (animProps & USER_CONTROLLED) != 0;
+    }
+
     /**
      * Returns the interpolator set for animId or fallback if nothing is set
      *
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index 2c834bd..ccff095 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -153,11 +153,13 @@
                 }, this::getCurrentActivity);
             }
 
-            case TestProtocol.REQUEST_IME_INSETS: {
+            case TestProtocol.REQUEST_SYSTEM_GESTURE_REGION: {
                 return getUIProperty(Bundle::putParcelable, activity -> {
                     WindowInsetsCompat insets = WindowInsetsCompat.toWindowInsetsCompat(
                             activity.getWindow().getDecorView().getRootWindowInsets());
-                    return insets.getInsets(WindowInsetsCompat.Type.ime()).toPlatformInsets();
+                    return insets.getInsets(WindowInsetsCompat.Type.ime()
+                            | WindowInsetsCompat.Type.systemGestures())
+                            .toPlatformInsets();
                 }, this::getCurrentActivity);
             }
 
@@ -330,6 +332,7 @@
                 return null;
             }
             T value = provider.apply(target);
+
             Bundle response = new Bundle();
             bundleSetter.set(response, TestProtocol.TEST_INFO_RESPONSE_FIELD, value);
             return response;
diff --git a/src/com/android/launcher3/touch/AllAppsSwipeController.java b/src/com/android/launcher3/touch/AllAppsSwipeController.java
index 8b9bc19..fe4a83b 100644
--- a/src/com/android/launcher3/touch/AllAppsSwipeController.java
+++ b/src/com/android/launcher3/touch/AllAppsSwipeController.java
@@ -192,7 +192,7 @@
     protected StateAnimationConfig getConfigForStates(LauncherState fromState,
             LauncherState toState) {
         StateAnimationConfig config = super.getConfigForStates(fromState, toState);
-        config.userControlled = true;
+        config.animProps |= StateAnimationConfig.USER_CONTROLLED;
         if (fromState == NORMAL && toState == ALL_APPS) {
             applyNormalToAllAppsAnimConfig(mLauncher, config);
         } else if (fromState == ALL_APPS && toState == NORMAL) {
@@ -209,13 +209,13 @@
             config.setInterpolator(ANIM_SCRIM_FADE,
                     Interpolators.reverse(ALL_APPS_SCRIM_RESPONDER));
             config.setInterpolator(ANIM_ALL_APPS_FADE, FINAL_FRAME);
-            if (!config.userControlled) {
+            if (!config.isUserControlled()) {
                 config.setInterpolator(ANIM_VERTICAL_PROGRESS, EMPHASIZED);
             }
             config.setInterpolator(ANIM_WORKSPACE_SCALE, DECELERATED_EASE);
             config.setInterpolator(ANIM_DEPTH, DECELERATED_EASE);
         } else {
-            if (config.userControlled) {
+            if (config.isUserControlled()) {
                 config.setInterpolator(ANIM_DEPTH, Interpolators.reverse(BLUR_MANUAL));
                 config.setInterpolator(ANIM_WORKSPACE_FADE,
                         Interpolators.reverse(WORKSPACE_FADE_MANUAL));
@@ -250,29 +250,32 @@
         if (launcher.getDeviceProfile().isTablet) {
             config.setInterpolator(ANIM_ALL_APPS_FADE, INSTANT);
             config.setInterpolator(ANIM_SCRIM_FADE, ALL_APPS_SCRIM_RESPONDER);
-            if (!config.userControlled) {
+            if (!config.isUserControlled()) {
                 config.setInterpolator(ANIM_VERTICAL_PROGRESS, EMPHASIZED);
             }
             config.setInterpolator(ANIM_WORKSPACE_SCALE, DECELERATED_EASE);
             config.setInterpolator(ANIM_DEPTH, DECELERATED_EASE);
         } else {
-            config.setInterpolator(ANIM_DEPTH, config.userControlled ? BLUR_MANUAL : BLUR_ATOMIC);
+            config.setInterpolator(ANIM_DEPTH,
+                    config.isUserControlled() ? BLUR_MANUAL : BLUR_ATOMIC);
             config.setInterpolator(ANIM_WORKSPACE_FADE,
-                    config.userControlled ? WORKSPACE_FADE_MANUAL : WORKSPACE_FADE_ATOMIC);
+                    config.isUserControlled() ? WORKSPACE_FADE_MANUAL : WORKSPACE_FADE_ATOMIC);
             config.setInterpolator(ANIM_WORKSPACE_SCALE,
-                    config.userControlled ? WORKSPACE_SCALE_MANUAL : WORKSPACE_SCALE_ATOMIC);
+                    config.isUserControlled() ? WORKSPACE_SCALE_MANUAL : WORKSPACE_SCALE_ATOMIC);
             config.setInterpolator(ANIM_HOTSEAT_FADE,
-                    config.userControlled ? HOTSEAT_FADE_MANUAL : HOTSEAT_FADE_ATOMIC);
+                    config.isUserControlled() ? HOTSEAT_FADE_MANUAL : HOTSEAT_FADE_ATOMIC);
             config.setInterpolator(ANIM_HOTSEAT_SCALE,
-                    config.userControlled ? HOTSEAT_SCALE_MANUAL : HOTSEAT_SCALE_ATOMIC);
+                    config.isUserControlled() ? HOTSEAT_SCALE_MANUAL : HOTSEAT_SCALE_ATOMIC);
             config.setInterpolator(ANIM_HOTSEAT_TRANSLATE,
-                    config.userControlled ? HOTSEAT_TRANSLATE_MANUAL : HOTSEAT_TRANSLATE_ATOMIC);
+                    config.isUserControlled()
+                            ? HOTSEAT_TRANSLATE_MANUAL
+                            : HOTSEAT_TRANSLATE_ATOMIC);
             config.setInterpolator(ANIM_SCRIM_FADE,
-                    config.userControlled ? SCRIM_FADE_MANUAL : SCRIM_FADE_ATOMIC);
+                    config.isUserControlled() ? SCRIM_FADE_MANUAL : SCRIM_FADE_ATOMIC);
             config.setInterpolator(ANIM_ALL_APPS_FADE,
-                    config.userControlled ? ALL_APPS_FADE_MANUAL : ALL_APPS_FADE_ATOMIC);
+                    config.isUserControlled() ? ALL_APPS_FADE_MANUAL : ALL_APPS_FADE_ATOMIC);
             config.setInterpolator(ANIM_VERTICAL_PROGRESS,
-                    config.userControlled
+                    config.isUserControlled()
                             ? ALL_APPS_VERTICAL_PROGRESS_MANUAL
                             : ALL_APPS_VERTICAL_PROGRESS_ATOMIC);
         }
@@ -285,7 +288,7 @@
      */
     public static void applyOverviewToAllAppsAnimConfig(
             DeviceProfile deviceProfile, StateAnimationConfig config, float threshold) {
-        config.userControlled = true;
+        config.animProps |= StateAnimationConfig.USER_CONTROLLED;
         config.animFlags = SKIP_OVERVIEW;
         if (deviceProfile.isTablet) {
             config.setInterpolator(ANIM_ALL_APPS_FADE, INSTANT);
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index a9c2a2e..ff8b381 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.touch;
 
+import static com.android.launcher3.Flags.enableSupportForArchiving;
 import static com.android.launcher3.LauncherConstants.ActivityCodes.REQUEST_BIND_PENDING_APPWIDGET;
 import static com.android.launcher3.LauncherConstants.ActivityCodes.REQUEST_RECONFIGURE_APPWIDGET;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_OPEN;
@@ -31,6 +32,7 @@
 import android.content.Intent;
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageInstaller.SessionInfo;
+import android.os.Process;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
@@ -58,8 +60,8 @@
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.shared.TestProtocol;
+import com.android.launcher3.uioverrides.ApiWrapper;
 import com.android.launcher3.util.ItemInfoMatcher;
-import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.views.FloatingIconView;
 import com.android.launcher3.views.Snackbar;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
@@ -207,7 +209,8 @@
                 }
             }
             // Fallback to using custom market intent.
-            Intent intent = new PackageManagerHelper(launcher).getMarketIntent(packageName);
+            Intent intent = ApiWrapper.getAppMarketActivityIntent(launcher,
+                    packageName, Process.myUserHandle());
             launcher.startActivitySafely(v, intent, item);
         };
 
@@ -317,7 +320,8 @@
         }
 
         // Check for abandoned promise
-        if ((v instanceof BubbleTextView) && shortcut.hasPromiseIconUi()) {
+        if ((v instanceof BubbleTextView) && shortcut.hasPromiseIconUi()
+                && (!enableSupportForArchiving() || !shortcut.isArchived())) {
             String packageName = shortcut.getIntent().getComponent() != null
                     ? shortcut.getIntent().getComponent().getPackageName()
                     : shortcut.getIntent().getPackage();
@@ -344,8 +348,8 @@
                 && (((ItemInfoWithIcon) item).runtimeStatusFlags
                 & ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE) != 0) {
             ItemInfoWithIcon appInfo = (ItemInfoWithIcon) item;
-            intent = new PackageManagerHelper(launcher)
-                    .getMarketIntent(appInfo.getTargetComponent().getPackageName());
+            intent = ApiWrapper.getAppMarketActivityIntent(launcher,
+                    appInfo.getTargetComponent().getPackageName(), Process.myUserHandle());
         } else {
             intent = item.getIntent();
         }
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index 51c047c..f7afcb9 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -543,15 +543,19 @@
 
         secondarySnapshotWidth = parentWidth;
         secondarySnapshotHeight = totalThumbnailHeight - primarySnapshotHeight - dividerBar;
-        secondarySnapshot.setTranslationY(0);
-        primarySnapshot.setTranslationY(secondarySnapshotHeight + spaceAboveSnapshot + dividerBar);
+
+        int translationY = primarySnapshotHeight + spaceAboveSnapshot + dividerBar;
+        primarySnapshot.setTranslationY(spaceAboveSnapshot);
+        secondarySnapshot.setTranslationY(translationY - spaceAboveSnapshot);
+
         primarySnapshot.measure(
                 View.MeasureSpec.makeMeasureSpec(primarySnapshotWidth, View.MeasureSpec.EXACTLY),
-                View.MeasureSpec.makeMeasureSpec(primarySnapshotHeight, View.MeasureSpec.EXACTLY));
+                View.MeasureSpec.makeMeasureSpec(primarySnapshotHeight, View.MeasureSpec.EXACTLY)
+        );
         secondarySnapshot.measure(
                 View.MeasureSpec.makeMeasureSpec(secondarySnapshotWidth, View.MeasureSpec.EXACTLY),
-                View.MeasureSpec.makeMeasureSpec(secondarySnapshotHeight,
-                        View.MeasureSpec.EXACTLY));
+                View.MeasureSpec.makeMeasureSpec(secondarySnapshotHeight, View.MeasureSpec.EXACTLY)
+        );
     }
 
     @Override
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index 04b6710..8301981 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -644,11 +644,12 @@
 
             secondarySnapshotHeight = totalThumbnailHeight;
             secondarySnapshotWidth = parentWidth - primarySnapshotWidth - scaledDividerBar;
-            int translationX = primarySnapshotWidth + scaledDividerBar;
             if (isRtl) {
+                int translationX = secondarySnapshotWidth + scaledDividerBar;
                 primarySnapshot.setTranslationX(-translationX);
                 secondarySnapshot.setTranslationX(0);
             } else {
+                int translationX = primarySnapshotWidth + scaledDividerBar;
                 secondarySnapshot.setTranslationX(translationX);
                 primarySnapshot.setTranslationX(0);
             }
@@ -744,7 +745,8 @@
             secondaryIconParams.setMarginStart(primaryIconParams.getMarginStart());
             if (deviceProfile.isLeftRightSplit) {
                 if (isRtl) {
-                    primaryIconView.setTranslationX(-primarySnapshotWidth);
+                    int secondarySnapshotWidth = groupedTaskViewWidth - primarySnapshotWidth;
+                    primaryIconView.setTranslationX(-secondarySnapshotWidth);
                 } else {
                     secondaryIconView.setTranslationX(primarySnapshotWidth);
                 }
diff --git a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
index 06526a8..7077ad9 100644
--- a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
@@ -285,7 +285,7 @@
         primaryIconView.setTranslationX(0);
         secondaryIconView.setTranslationX(0);
         if (enableOverviewIconMenu()) {
-            if (primaryIconView.getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
+            if (isRtl) {
                 primaryIconView.setTranslationY(groupedTaskViewHeight - primarySnapshotHeight);
                 secondaryIconView.setTranslationY(0);
             } else {
@@ -295,14 +295,25 @@
         } else if (splitConfig.initiatedFromSeascape) {
             // if the split was initiated from seascape,
             // the task on the right (secondary) is slightly larger
-            primaryIconView.setTranslationY(-bottomToMidpointOffset - insetOffset
-                    + taskIconHeight);
-            secondaryIconView.setTranslationY(-bottomToMidpointOffset - insetOffset);
+            if (isRtl) {
+                primaryIconView.setTranslationY(bottomToMidpointOffset - insetOffset
+                        + taskIconHeight);
+                secondaryIconView.setTranslationY(bottomToMidpointOffset - insetOffset);
+            } else {
+                primaryIconView.setTranslationY(-bottomToMidpointOffset - insetOffset
+                        + taskIconHeight);
+                secondaryIconView.setTranslationY(-bottomToMidpointOffset - insetOffset);
+            }
         } else {
             // if not,
             // the task on the left (primary) is slightly larger
-            primaryIconView.setTranslationY(-bottomToMidpointOffset + taskIconHeight);
-            secondaryIconView.setTranslationY(-bottomToMidpointOffset);
+            if (isRtl) {
+                primaryIconView.setTranslationY(bottomToMidpointOffset + taskIconHeight);
+                secondaryIconView.setTranslationY(bottomToMidpointOffset);
+            } else {
+                primaryIconView.setTranslationY(-bottomToMidpointOffset + taskIconHeight);
+                secondaryIconView.setTranslationY(-bottomToMidpointOffset);
+            }
         }
 
         primaryIconView.setLayoutParams(primaryIconParams);
@@ -343,13 +354,15 @@
         secondarySnapshotHeight = totalThumbnailHeight - primarySnapshotHeight - dividerBar;
         secondarySnapshot.setTranslationY(0);
         primarySnapshot.setTranslationY(secondarySnapshotHeight + spaceAboveSnapshot + dividerBar);
+
         primarySnapshot.measure(
                 View.MeasureSpec.makeMeasureSpec(primarySnapshotWidth, View.MeasureSpec.EXACTLY),
-                View.MeasureSpec.makeMeasureSpec(primarySnapshotHeight, View.MeasureSpec.EXACTLY));
+                View.MeasureSpec.makeMeasureSpec(primarySnapshotHeight, View.MeasureSpec.EXACTLY)
+        );
         secondarySnapshot.measure(
                 View.MeasureSpec.makeMeasureSpec(secondarySnapshotWidth, View.MeasureSpec.EXACTLY),
-                View.MeasureSpec.makeMeasureSpec(secondarySnapshotHeight,
-                        View.MeasureSpec.EXACTLY));
+                View.MeasureSpec.makeMeasureSpec(secondarySnapshotHeight, View.MeasureSpec.EXACTLY)
+        );
     }
 
     /* ---------- The following are only used by TaskViewTouchHandler. ---------- */
diff --git a/src/com/android/launcher3/util/CannedAnimationCoordinator.kt b/src/com/android/launcher3/util/CannedAnimationCoordinator.kt
index 18f8339..85f81f5 100644
--- a/src/com/android/launcher3/util/CannedAnimationCoordinator.kt
+++ b/src/com/android/launcher3/util/CannedAnimationCoordinator.kt
@@ -26,6 +26,8 @@
 import com.android.launcher3.anim.AnimatorPlaybackController
 import com.android.launcher3.anim.PendingAnimation
 import com.android.launcher3.statemanager.StatefulActivity
+import com.android.launcher3.states.StateAnimationConfig.HANDLE_STATE_APPLY
+import com.android.launcher3.states.StateAnimationConfig.USER_CONTROLLED
 import java.util.function.Consumer
 
 private const val TAG = "CannedAnimCoordinator"
@@ -107,8 +109,15 @@
         }
         // Link this to the state manager so that it auto-cancels when state changes
         recreatePending = false
+        // Animator coordinator takes care of reapplying the animation due to state reset. Set the
+        // flags accordingly
         animationController =
-            controller.apply { activity.stateManager.setCurrentUserControlledAnimation(this) }
+            controller.apply {
+                activity.stateManager.setCurrentAnimation(
+                    this,
+                    USER_CONTROLLED or HANDLE_STATE_APPLY
+                )
+            }
         recreateAnimation(provider)
     }
 
diff --git a/src/com/android/launcher3/util/CellContentDimensions.kt b/src/com/android/launcher3/util/CellContentDimensions.kt
index 3c8e0c4..5059c2f 100644
--- a/src/com/android/launcher3/util/CellContentDimensions.kt
+++ b/src/com/android/launcher3/util/CellContentDimensions.kt
@@ -48,7 +48,10 @@
             cellContentHeight = getCellContentHeight()
 
             // Step 3. Decrease label size
-            if (cellContentHeight > cellHeightPx) {
+            if (
+                cellContentHeight > cellHeightPx &&
+                    iconTextSizePx > iconSizeSteps.minimumIconLabelSize
+            ) {
                 iconTextSizePx =
                     max(
                         iconSizeSteps.minimumIconLabelSize,
@@ -58,6 +61,17 @@
             }
         }
 
+        // For some cases, depending on the display size, the content might not fit inside the
+        // cell height after considering the minimum icon and label size allowed.
+        // For these extreme cases, we will allow the icon size to be smaller than
+        // [IconSizeSteps.minimumIconSize] to fit inside the cell height without cropping.
+        while (
+            cellContentHeight > cellHeightPx && iconSizePx > IconSizeSteps.ICON_SIZE_STEP_EXTRA
+        ) {
+            iconSizePx -= IconSizeSteps.ICON_SIZE_STEP_EXTRA
+            cellContentHeight = getCellContentHeight()
+        }
+
         return cellContentHeight
     }
 
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index 18f583d..1419dc4 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -175,6 +175,13 @@
         sTransientTaskbarStatusForTests = enable;
     }
 
+    /**
+     * Returns whether the taskbar is pinned in gesture navigation mode.
+     */
+    public static boolean isPinnedTaskbar(Context context) {
+        return INSTANCE.get(context).getInfo().isPinnedTaskbar();
+    }
+
     @Override
     public void close() {
         mDestroyed = true;
@@ -423,6 +430,12 @@
             }
             return true;
         }
+        /**
+         * Returns whether the taskbar is pinned in gesture navigation mode.
+         */
+        public boolean isPinnedTaskbar() {
+            return navigationMode == NavigationMode.NO_BUTTON && !isTransientTaskbar();
+        }
 
         /**
          * Returns {@code true} if the bounds represent a tablet.
diff --git a/src/com/android/launcher3/util/IconSizeSteps.kt b/src/com/android/launcher3/util/IconSizeSteps.kt
index 6128eb4..a207d5c 100644
--- a/src/com/android/launcher3/util/IconSizeSteps.kt
+++ b/src/com/android/launcher3/util/IconSizeSteps.kt
@@ -49,5 +49,9 @@
 
     companion object {
         internal const val TEXT_STEP = 1
+
+        // This icon extra step is used for stepping down logic in extreme cases when it's
+        // necessary to reduce the icon size below minimum size available in [icon_size_steps].
+        internal const val ICON_SIZE_STEP_EXTRA = 2
     }
 }
diff --git a/src/com/android/launcher3/util/KeyboardShortcutsDelegate.java b/src/com/android/launcher3/util/KeyboardShortcutsDelegate.java
index c9db83d..e4e0bae 100644
--- a/src/com/android/launcher3/util/KeyboardShortcutsDelegate.java
+++ b/src/com/android/launcher3/util/KeyboardShortcutsDelegate.java
@@ -17,6 +17,7 @@
 
 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.accessibility.LauncherAccessibilityDelegate.getSupportedActions;
 
 import android.util.Log;
@@ -27,6 +28,7 @@
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.accessibility.BaseAccessibilityDelegate;
@@ -118,17 +120,22 @@
                 return true;
             } else if (mLauncher.getAppsView().isInAllApps()) {
                 // Close all apps if there are no open floating views.
-                closeAllApps();
+                mLauncher.getStateManager().goToState(NORMAL, true);
+                return true;
+            } else if (mLauncher.isInState(LauncherState.OVERVIEW)
+                    || mLauncher.isInState(LauncherState.OVERVIEW_SPLIT_SELECT)) {
+                // Close Overview and return to home.
+                mLauncher.getStateManager().goToState(NORMAL, true);
+                return true;
+            } else if (mLauncher.isInState(LauncherState.OVERVIEW_MODAL_TASK)) {
+                // Return to the previous state (Overview) when the modal task is open.
+                mLauncher.getStateManager().goToState(OVERVIEW, true);
                 return true;
             }
         }
         return null;
     }
 
-    private void closeAllApps() {
-        mLauncher.getStateManager().goToState(NORMAL, true);
-    }
-
     /**
      * Handle key up event.
      * @param keyCode code of the key being pressed.
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index 91203a7..3f7a128 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -28,8 +28,8 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
 import android.graphics.Rect;
-import android.net.Uri;
 import android.os.Bundle;
+import android.os.Process;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
@@ -46,6 +46,7 @@
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.uioverrides.ApiWrapper;
 
 import java.net.URISyntaxException;
 import java.util.List;
@@ -137,17 +138,6 @@
         return (info.flags & ApplicationInfo.FLAG_SUSPENDED) != 0;
     }
 
-    public Intent getMarketIntent(String packageName) {
-        return new Intent(Intent.ACTION_VIEW)
-                .setData(new Uri.Builder()
-                        .scheme("market")
-                        .authority("details")
-                        .appendQueryParameter("id", packageName)
-                        .build())
-                .putExtra(Intent.EXTRA_REFERRER, new Uri.Builder().scheme("android-app")
-                        .authority(mContext.getPackageName()).build());
-    }
-
     /**
      * Creates a new market search intent.
      */
@@ -172,8 +162,8 @@
                 && (((ItemInfoWithIcon) info).runtimeStatusFlags
                     & ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE) != 0) {
             ItemInfoWithIcon appInfo = (ItemInfoWithIcon) info;
-            mContext.startActivity(new PackageManagerHelper(mContext)
-                    .getMarketIntent(appInfo.getTargetComponent().getPackageName()));
+            mContext.startActivity(ApiWrapper.getAppMarketActivityIntent(mContext,
+                    appInfo.getTargetComponent().getPackageName(), Process.myUserHandle()));
             return;
         }
         ComponentName componentName = null;
diff --git a/src/com/android/launcher3/views/ClipIconView.java b/src/com/android/launcher3/views/ClipIconView.java
index 87e496e..7737adb 100644
--- a/src/com/android/launcher3/views/ClipIconView.java
+++ b/src/com/android/launcher3/views/ClipIconView.java
@@ -112,7 +112,7 @@
         float scaleY = rect.height() / minSize;
         float scale = Math.max(1f, Math.min(scaleX, scaleY));
 
-        if (Float.isNaN(scale)) {
+        if (Float.isNaN(scale) || Float.isInfinite(scale)) {
             // Views are no longer laid out, do not update.
             return;
         }
diff --git a/src/com/android/launcher3/widget/DeferredAppWidgetHostView.java b/src/com/android/launcher3/widget/DeferredAppWidgetHostView.java
deleted file mode 100644
index f42142e..0000000
--- a/src/com/android/launcher3/widget/DeferredAppWidgetHostView.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.widget;
-
-import android.appwidget.AppWidgetProviderInfo;
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.text.Layout;
-import android.text.StaticLayout;
-import android.text.TextPaint;
-import android.text.TextUtils;
-import android.util.TypedValue;
-import android.view.View;
-import android.widget.RemoteViews;
-
-import com.android.launcher3.R;
-
-/**
- * A widget host views created while the host has not bind to the system service.
- */
-public class DeferredAppWidgetHostView extends LauncherAppWidgetHostView {
-
-    private final TextPaint mPaint;
-    private Layout mSetupTextLayout;
-
-    public DeferredAppWidgetHostView(Context context) {
-        super(context);
-        setWillNotDraw(false);
-
-        mPaint = new TextPaint();
-        mPaint.setColor(Color.WHITE);
-        mPaint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX,
-                mLauncher.getDeviceProfile().iconTextSizePx,
-                getResources().getDisplayMetrics()));
-        setBackgroundResource(R.drawable.bg_deferred_app_widget);
-    }
-
-    @Override
-    public void updateAppWidget(RemoteViews remoteViews) {
-        // Not allowed
-    }
-
-    @Override
-    public void addView(View child) {
-        // Not allowed
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
-        AppWidgetProviderInfo info = getAppWidgetInfo();
-        if (info == null || TextUtils.isEmpty(info.label)) {
-            return;
-        }
-
-        // Use double padding so that there is extra space between background and text if possible.
-        int availableWidth = getMeasuredWidth() - 2 * (getPaddingLeft() + getPaddingRight());
-        if (availableWidth <= 0) {
-            availableWidth = getMeasuredWidth() - (getPaddingLeft() + getPaddingRight());
-        }
-        if (mSetupTextLayout != null && mSetupTextLayout.getText().equals(info.label)
-                && mSetupTextLayout.getWidth() == availableWidth) {
-            return;
-        }
-        mSetupTextLayout = new StaticLayout(info.label, mPaint, availableWidth,
-                Layout.Alignment.ALIGN_CENTER, 1, 0, true);
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas) {
-        if (mSetupTextLayout != null) {
-            canvas.translate((getWidth() - mSetupTextLayout.getWidth()) / 2,
-                    (getHeight() - mSetupTextLayout.getHeight()) / 2);
-            mSetupTextLayout.draw(canvas);
-        }
-    }
-}
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHost.java b/src/com/android/launcher3/widget/LauncherAppWidgetHost.java
index 9c21ea2..b1c477c 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHost.java
@@ -21,13 +21,22 @@
 import android.appwidget.AppWidgetHost;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.RemoteViews;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.SafeCloseable;
+import com.android.launcher3.widget.LauncherWidgetHolder.ProviderChangedListener;
 
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.WeakHashMap;
 import java.util.function.IntConsumer;
 
 /**
@@ -37,8 +46,7 @@
  */
 class LauncherAppWidgetHost extends AppWidgetHost {
     @NonNull
-    private final ArrayList<LauncherWidgetHolder.ProviderChangedListener>
-            mProviderChangeListeners = new ArrayList<>();
+    private final List<ProviderChangedListener> mProviderChangeListeners;
 
     @NonNull
     private final Context mContext;
@@ -46,33 +54,13 @@
     @Nullable
     private final IntConsumer mAppWidgetRemovedCallback;
 
-    @NonNull
-    private final LauncherWidgetHolder mHolder;
-
     public LauncherAppWidgetHost(@NonNull Context context,
-            @Nullable IntConsumer appWidgetRemovedCallback, @NonNull LauncherWidgetHolder holder) {
+            @Nullable IntConsumer appWidgetRemovedCallback,
+            List<ProviderChangedListener> providerChangeListeners) {
         super(context, APPWIDGET_HOST_ID);
         mContext = context;
         mAppWidgetRemovedCallback = appWidgetRemovedCallback;
-        mHolder = holder;
-    }
-
-    /**
-     * Add a listener that is triggered when the providers of the widgets are changed
-     * @param listener The listener that notifies when the providers changed
-     */
-    public void addProviderChangeListener(
-            @NonNull LauncherWidgetHolder.ProviderChangedListener listener) {
-        mProviderChangeListeners.add(listener);
-    }
-
-    /**
-     * Remove the specified listener from the host
-     * @param listener The listener that is to be removed from the host
-     */
-    public void removeProviderChangeListener(
-            LauncherWidgetHolder.ProviderChangedListener listener) {
-        mProviderChangeListeners.remove(listener);
+        mProviderChangeListeners = providerChangeListeners;
     }
 
     @Override
@@ -89,7 +77,7 @@
     @NonNull
     public LauncherAppWidgetHostView onCreateView(Context context, int appWidgetId,
             AppWidgetProviderInfo appWidget) {
-        return mHolder.onCreateView(context, appWidgetId, appWidget);
+        return new ListenableHostView(context);
     }
 
     /**
@@ -115,7 +103,10 @@
         if (mAppWidgetRemovedCallback == null) {
             return;
         }
-        mAppWidgetRemovedCallback.accept(appWidgetId);
+        // Route the call via model thread, in case it comes while a loader-bind is in progress
+        Executors.MODEL_EXECUTOR.execute(
+                () -> Executors.MAIN_EXECUTOR.execute(
+                        () -> mAppWidgetRemovedCallback.accept(appWidgetId)));
     }
 
     /**
@@ -126,4 +117,36 @@
         super.clearViews();
     }
 
+    public static class ListenableHostView extends LauncherAppWidgetHostView {
+
+        private Set<Runnable> mUpdateListeners = Collections.EMPTY_SET;
+
+        ListenableHostView(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void updateAppWidget(RemoteViews remoteViews) {
+            super.updateAppWidget(remoteViews);
+            mUpdateListeners.forEach(Runnable::run);
+        }
+
+        @Override
+        public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+            super.onInitializeAccessibilityNodeInfo(info);
+            info.setClassName(LauncherAppWidgetHostView.class.getName());
+        }
+
+        /**
+         * Adds a callback to be run everytime the provided app widget updates.
+         * @return a closable to remove this callback
+         */
+        public SafeCloseable addUpdateListener(Runnable callback) {
+            if (mUpdateListeners == Collections.EMPTY_SET) {
+                mUpdateListeners = Collections.newSetFromMap(new WeakHashMap<>());
+            }
+            mUpdateListeners.add(callback);
+            return () -> mUpdateListeners.remove(callback);
+        }
+    }
 }
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index 5d069ed..e0de269 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -41,10 +41,10 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.CheckLongPressHelper;
+import com.android.launcher3.Flags;
 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;
@@ -95,14 +95,15 @@
 
     private boolean mTrackingWidgetUpdate = false;
 
-    private boolean mIsWidgetCachingDisabled = false;
-
     public LauncherAppWidgetHostView(Context context) {
         super(context);
         mLauncher = Launcher.getLauncher(context);
         mLongPressHelper = new CheckLongPressHelper(this, this);
         setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
         setBackgroundResource(R.drawable.widget_internal_focus_bg);
+        if (Flags.enableFocusOutline()) {
+            setDefaultFocusHighlightEnabled(false);
+        }
 
         if (Utilities.ATLEAST_Q && Themes.getAttrBoolean(mLauncher, R.attr.isWorkspaceDarkText)) {
             setOnLightBackground(true);
@@ -140,10 +141,6 @@
         }
     }
 
-    public void setIsWidgetCachingDisabled(boolean isWidgetCachingDisabled) {
-        mIsWidgetCachingDisabled = isWidgetCachingDisabled;
-    }
-
     @Override
     @TargetApi(Build.VERSION_CODES.Q)
     public void updateAppWidget(RemoteViews remoteViews) {
@@ -153,19 +150,11 @@
                     TRACE_METHOD_NAME + getAppWidgetInfo().provider, getAppWidgetId());
             mTrackingWidgetUpdate = false;
         }
-        if (FeatureFlags.ENABLE_CACHED_WIDGET.get()
-                && !mIsWidgetCachingDisabled) {
+        if (isDeferringUpdates()) {
             mLastRemoteViews = remoteViews;
-            if (isDeferringUpdates()) {
-                return;
-            }
-        } else {
-            if (isDeferringUpdates()) {
-                mLastRemoteViews = remoteViews;
-                return;
-            }
-            mLastRemoteViews = null;
+            return;
         }
+        mLastRemoteViews = null;
 
         super.updateAppWidget(remoteViews);
 
@@ -434,22 +423,6 @@
         scheduleNextAdvance();
     }
 
-    public void reInflate() {
-        if (!isAttachedToWindow()) {
-            return;
-        }
-        LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) getTag();
-        if (info == null) {
-            // This occurs when LauncherAppWidgetHostView is used to render a preview layout.
-            return;
-        }
-        // Remove and rebind the current widget (which was inflated in the wrong
-        // orientation), but don't delete it from the database
-        mLauncher.removeItem(this, info, false  /* deleteFromDb */,
-                "widget removed because of configuration change");
-        mLauncher.bindAppWidget(info);
-    }
-
     @Override
     protected boolean shouldAllowDirectClick() {
         if (getTag() instanceof ItemInfo) {
diff --git a/src/com/android/launcher3/widget/LauncherWidgetHolder.java b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
index 6acc83d..23127b3 100644
--- a/src/com/android/launcher3/widget/LauncherWidgetHolder.java
+++ b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
@@ -28,7 +28,6 @@
 import android.content.Intent;
 import android.os.Bundle;
 import android.util.SparseArray;
-import android.widget.RemoteViews;
 import android.widget.Toast;
 
 import androidx.annotation.NonNull;
@@ -36,17 +35,19 @@
 
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.util.ResourceBasedOverride;
+import com.android.launcher3.util.SafeCloseable;
+import com.android.launcher3.widget.LauncherAppWidgetHost.ListenableHostView;
 import com.android.launcher3.widget.custom.CustomWidgetManager;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.function.IntConsumer;
 
 /**
@@ -64,17 +65,14 @@
             FLAG_STATE_IS_NORMAL | FLAG_ACTIVITY_STARTED | FLAG_ACTIVITY_RESUMED;
 
     @NonNull
-    private final Context mContext;
+    protected final Context mContext;
 
     @NonNull
     private final AppWidgetHost mWidgetHost;
 
     @NonNull
-    private final SparseArray<LauncherAppWidgetHostView> mViews = new SparseArray<>();
-    @NonNull
-    private final SparseArray<PendingAppWidgetHostView> mPendingViews = new SparseArray<>();
-    @NonNull
-    private final SparseArray<LauncherAppWidgetHostView> mDeferredViews = new SparseArray<>();
+    protected final SparseArray<LauncherAppWidgetHostView> mViews = new SparseArray<>();
+    protected final List<ProviderChangedListener> mProviderChangedListeners = new ArrayList<>();
 
     protected int mFlags = FLAG_STATE_IS_NORMAL;
 
@@ -91,7 +89,8 @@
 
     protected AppWidgetHost createHost(
             Context context, @Nullable IntConsumer appWidgetRemovedCallback) {
-        return new LauncherAppWidgetHost(context, appWidgetRemovedCallback, this);
+        return new LauncherAppWidgetHost(
+                context, appWidgetRemovedCallback, mProviderChangedListeners);
     }
 
     /**
@@ -121,25 +120,12 @@
      * Update any views which have been deferred because the host was not listening.
      */
     protected void updateDeferredView() {
+        // Update any views which have been deferred because the host was not listening.
         // 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.
-                    mWidgetHost.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);
-                }
+            if (view instanceof PendingAppWidgetHostView pv) {
+                pv.reInflate();
             }
         }
     }
@@ -173,34 +159,6 @@
     public void deleteAppWidgetId(int appWidgetId) {
         mWidgetHost.deleteAppWidgetId(appWidgetId);
         mViews.remove(appWidgetId);
-        if (FeatureFlags.ENABLE_CACHED_WIDGET.get()) {
-            final LauncherAppState state = LauncherAppState.getInstance(mContext);
-            synchronized (state.mCachedRemoteViews) {
-                state.mCachedRemoteViews.delete(appWidgetId);
-            }
-        }
-    }
-
-    /**
-     * Add the pending view to the host for complete configuration in further steps
-     * @param appWidgetId The ID of the specified app widget
-     * @param view The {@link PendingAppWidgetHostView} of the app widget
-     */
-    public void addPendingView(int appWidgetId, @NonNull PendingAppWidgetHostView view) {
-        mPendingViews.put(appWidgetId, view);
-    }
-
-    /**
-     * @param appWidgetId The app widget id of the specified widget
-     * @return The {@link PendingAppWidgetHostView} of the widget if it exists, null otherwise
-     */
-    @Nullable
-    protected PendingAppWidgetHostView getPendingView(int appWidgetId) {
-        return mPendingViews.get(appWidgetId);
-    }
-
-    protected void removePendingView(int appWidgetId) {
-        mPendingViews.remove(appWidgetId);
     }
 
     /**
@@ -225,18 +183,18 @@
      * Add a listener that is triggered when the providers of the widgets are changed
      * @param listener The listener that notifies when the providers changed
      */
-    public void addProviderChangeListener(@NonNull ProviderChangedListener listener) {
-        LauncherAppWidgetHost tempHost = (LauncherAppWidgetHost) mWidgetHost;
-        tempHost.addProviderChangeListener(listener);
+    public void addProviderChangeListener(
+            @NonNull LauncherWidgetHolder.ProviderChangedListener listener) {
+        MAIN_EXECUTOR.execute(() -> mProviderChangedListeners.add(listener));
     }
 
     /**
      * Remove the specified listener from the host
      * @param listener The listener that is to be removed from the host
      */
-    public void removeProviderChangeListener(ProviderChangedListener listener) {
-        LauncherAppWidgetHost tempHost = (LauncherAppWidgetHost) mWidgetHost;
-        tempHost.removeProviderChangeListener(listener);
+    public void removeProviderChangeListener(
+            LauncherWidgetHolder.ProviderChangedListener listener) {
+        MAIN_EXECUTOR.execute(() -> mProviderChangedListeners.remove(listener));
     }
 
     /**
@@ -319,17 +277,6 @@
         if (WidgetsModel.GO_DISABLE_WIDGETS) {
             return;
         }
-        if (FeatureFlags.ENABLE_CACHED_WIDGET.get()) {
-            // Cache the content from the widgets when Launcher stops listening to widget updates
-            final LauncherAppState state = LauncherAppState.getInstance(mContext);
-            synchronized (state.mCachedRemoteViews) {
-                for (int i = 0; i < mViews.size(); i++) {
-                    final int appWidgetId = mViews.keyAt(i);
-                    final LauncherAppWidgetHostView view = mViews.get(appWidgetId);
-                    state.mCachedRemoteViews.put(appWidgetId, view.mLastRemoteViews);
-                }
-            }
-        }
         mWidgetHost.stopListening();
         setListeningFlag(false);
     }
@@ -351,47 +298,51 @@
     }
 
     /**
+     * Adds a callback to be run everytime the provided app widget updates.
+     * @return a closable to remove this callback
+     */
+    public SafeCloseable addOnUpdateListener(
+            int appWidgetId, LauncherAppWidgetProviderInfo appWidget, Runnable callback) {
+        if (createView(appWidgetId, appWidget) instanceof ListenableHostView lhv) {
+            return lhv.addUpdateListener(callback);
+        }
+        return () -> { };
+    }
+
+    /**
      * Create a view for the specified app widget
-     * @param context The activity context for which the view is created
+     *
      * @param appWidgetId The ID of the widget
-     * @param appWidget The {@link LauncherAppWidgetProviderInfo} of the widget
+     * @param appWidget   The {@link LauncherAppWidgetProviderInfo} of the widget
      * @return A view for the widget
      */
     @NonNull
-    public AppWidgetHostView createView(@NonNull Context context, int appWidgetId,
-            @NonNull LauncherAppWidgetProviderInfo appWidget) {
+    public AppWidgetHostView createView(
+            int appWidgetId, @NonNull LauncherAppWidgetProviderInfo appWidget) {
         if (appWidget.isCustomWidget()) {
-            LauncherAppWidgetHostView lahv = new LauncherAppWidgetHostView(context);
+            LauncherAppWidgetHostView lahv = new LauncherAppWidgetHostView(mContext);
             lahv.setAppWidget(0, appWidget);
-            CustomWidgetManager.INSTANCE.get(context).onViewCreated(lahv);
+            CustomWidgetManager.INSTANCE.get(mContext).onViewCreated(lahv);
             return lahv;
-        } else if ((mFlags & FLAG_LISTENING) == 0) {
+        }
+
+        LauncherAppWidgetHostView view = createViewInternal(appWidgetId, appWidget);
+        mViews.put(appWidgetId, view);
+        return view;
+    }
+
+    @NonNull
+    protected LauncherAppWidgetHostView createViewInternal(
+            int appWidgetId, @NonNull LauncherAppWidgetProviderInfo appWidget) {
+        if ((mFlags & FLAG_LISTENING) == 0) {
             // 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
+            // host.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()) {
-                final RemoteViews cachedRemoteViews = getCachedRemoteViews(appWidgetId);
-                if (cachedRemoteViews != 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(cachedRemoteViews);
-                    mDeferredViews.put(appWidgetId, view);
-                    mViews.put(appWidgetId, view);
-                    return view;
-                }
-            }
-            // If cache misses or not enabled, a placeholder for the widget will be returned.
-            DeferredAppWidgetHostView view = new DeferredAppWidgetHostView(context);
-            view.setAppWidget(appWidgetId, appWidget);
-            mViews.put(appWidgetId, view);
-            return view;
+            return new PendingAppWidgetHostView(mContext, appWidgetId, appWidget);
         } else {
             try {
-                return mWidgetHost.createView(context, appWidgetId, appWidget);
+                return (LauncherAppWidgetHostView) mWidgetHost.createView(
+                        mContext, appWidgetId, appWidget);
             } catch (Exception e) {
                 if (!Utilities.isBinderSizeError(e)) {
                     throw new RuntimeException(e);
@@ -402,7 +353,7 @@
                 // will update.
                 LauncherAppWidgetHostView view = mViews.get(appWidgetId);
                 if (view == null) {
-                    view = onCreateView(mContext, appWidgetId, appWidget);
+                    view = new ListenableHostView(mContext);
                 }
                 view.setAppWidget(appWidgetId, appWidget);
                 view.switchToErrorView();
@@ -422,41 +373,11 @@
     }
 
     /**
-     * Called to return a proper view when creating a view
-     * @param context The context for which the widget view is created
-     * @param appWidgetId The ID of the added widget
-     * @param appWidget The provider info of the added widget
-     * @return A view for the specified app widget
-     */
-    @NonNull
-    public LauncherAppWidgetHostView onCreateView(Context context, int appWidgetId,
-            AppWidgetProviderInfo appWidget) {
-        final LauncherAppWidgetHostView view;
-        if (getPendingView(appWidgetId) != null) {
-            view = getPendingView(appWidgetId);
-            removePendingView(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);
-        }
-        mViews.put(appWidgetId, view);
-        return view;
-    }
-
-    /**
      * Clears all the views from the host
      */
     public void clearViews() {
         LauncherAppWidgetHost tempHost = (LauncherAppWidgetHost) mWidgetHost;
         tempHost.clearViews();
-        if (FeatureFlags.ENABLE_CACHED_WIDGET.get()) {
-            // Clear previously cached content from existing widgets
-            mDeferredViews.clear();
-        }
         mViews.clear();
     }
 
@@ -496,14 +417,6 @@
         return (flags & FLAGS_SHOULD_LISTEN) == FLAGS_SHOULD_LISTEN;
     }
 
-    @Nullable
-    private RemoteViews getCachedRemoteViews(int appWidgetId) {
-        final LauncherAppState state = LauncherAppState.getInstance(mContext);
-        synchronized (state.mCachedRemoteViews) {
-            return state.mCachedRemoteViews.get(appWidgetId);
-        }
-    }
-
     /**
      * Returns the new LauncherWidgetHolder instance
      */
diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
index 1c88c4a..25979c2 100644
--- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
@@ -18,17 +18,20 @@
 
 import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon;
 import static com.android.launcher3.icons.FastBitmapDrawable.getDisabledColorFilter;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.text.Layout;
 import android.text.StaticLayout;
 import android.text.TextPaint;
+import android.text.TextUtils;
 import android.util.SizeF;
 import android.util.TypedValue;
 import android.view.ContextThemeWrapper;
@@ -36,17 +39,18 @@
 import android.view.View.OnClickListener;
 import android.widget.RemoteViews;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.icons.FastBitmapDrawable;
-import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.PackageItemInfo;
-import com.android.launcher3.touch.ItemClickHandler;
+import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.Themes;
 
 import java.util.List;
@@ -56,11 +60,24 @@
     private static final float SETUP_ICON_SIZE_FACTOR = 2f / 5;
     private static final float MIN_SATUNATION = 0.7f;
 
+    private static final int FLAG_DRAW_SETTINGS = 1;
+    private static final int FLAG_DRAW_ICON = 2;
+    private static final int FLAG_DRAW_LABEL = 4;
+
+    private static final int DEFERRED_ALPHA = 0x77;
+
     private final Rect mRect = new Rect();
-    private OnClickListener mClickListener;
+
+    private final LauncherAppWidgetProviderInfo mAppwidget;
     private final LauncherAppWidgetInfo mInfo;
     private final int mStartState;
     private final boolean mDisabledForSafeMode;
+    private final CharSequence mLabel;
+
+    private OnClickListener mClickListener;
+    private SafeCloseable mOnDetachCleanup;
+
+    private int mDragFlags;
 
     private Drawable mCenterDrawable;
     private Drawable mSettingIconDrawable;
@@ -71,19 +88,9 @@
     private Layout mSetupTextLayout;
 
     public PendingAppWidgetHostView(Context context, LauncherAppWidgetInfo info,
-            IconCache cache, boolean disabledForSafeMode) {
-        super(new ContextThemeWrapper(context, R.style.WidgetContainerTheme));
-
-        mInfo = info;
-        mStartState = info.restoreStatus;
-        mDisabledForSafeMode = disabledForSafeMode;
-
-        mPaint = new TextPaint();
-        mPaint.setColor(Themes.getAttrColor(getContext(), android.R.attr.textColorPrimary));
-        mPaint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX,
-                mLauncher.getDeviceProfile().iconTextSizePx, getResources().getDisplayMetrics()));
-        setBackgroundResource(R.drawable.pending_widget_bg);
-        setWillNotDraw(false);
+            @Nullable LauncherAppWidgetProviderInfo appWidget) {
+        this(context, info, appWidget,
+                context.getResources().getText(R.string.gadget_complete_setup_text));
 
         super.updateAppWidget(null);
         setOnClickListener(mLauncher.getItemOnClickListener());
@@ -91,22 +98,102 @@
         if (info.pendingItemInfo == null) {
             info.pendingItemInfo = new PackageItemInfo(info.providerName.getPackageName(),
                     info.user);
-            cache.updateIconInBackground(this, info.pendingItemInfo);
+            LauncherAppState.getInstance(context).getIconCache()
+                    .updateIconInBackground(this, info.pendingItemInfo);
         } else {
             reapplyItemInfo(info.pendingItemInfo);
         }
     }
 
+    public PendingAppWidgetHostView(
+            Context context, int appWidgetId, @NonNull LauncherAppWidgetProviderInfo appWidget) {
+        this(context, new LauncherAppWidgetInfo(appWidgetId, appWidget.provider),
+                appWidget, appWidget.label);
+        getBackground().mutate().setAlpha(DEFERRED_ALPHA);
+
+        mCenterDrawable = new ColorDrawable(Color.TRANSPARENT);
+        mDragFlags = FLAG_DRAW_LABEL;
+        mDrawableSizeChanged = true;
+    }
+
+    private PendingAppWidgetHostView(Context context, LauncherAppWidgetInfo info,
+            LauncherAppWidgetProviderInfo appwidget, CharSequence label) {
+        super(new ContextThemeWrapper(context, R.style.WidgetContainerTheme));
+
+        mAppwidget = appwidget;
+        mInfo = info;
+        mStartState = info.restoreStatus;
+        mDisabledForSafeMode = LauncherAppState.getInstance(context).isSafeModeEnabled();
+        mLabel = label;
+
+        mPaint = new TextPaint();
+        mPaint.setColor(Themes.getAttrColor(getContext(), android.R.attr.textColorPrimary));
+        mPaint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX,
+                mLauncher.getDeviceProfile().iconTextSizePx, getResources().getDisplayMetrics()));
+
+        setWillNotDraw(false);
+        setBackgroundResource(R.drawable.pending_widget_bg);
+    }
+
     @Override
     public void updateAppWidget(RemoteViews remoteViews) {
+        checkIfRestored();
+    }
+
+    private void checkIfRestored() {
         WidgetManagerHelper widgetManagerHelper = new WidgetManagerHelper(getContext());
         if (widgetManagerHelper.isAppWidgetRestored(mInfo.appWidgetId)) {
-            super.updateAppWidget(remoteViews);
-            reInflate();
+            MAIN_EXECUTOR.getHandler().post(this::reInflate);
         }
     }
 
     @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        if ((mAppwidget != null)
+                && !mInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)
+                && mInfo.restoreStatus != LauncherAppWidgetInfo.RESTORE_COMPLETED) {
+            // If the widget is not completely restored, but has a valid ID, then listen of
+            // updates from provider app for potential restore complete.
+            if (mOnDetachCleanup != null) {
+                mOnDetachCleanup.close();
+            }
+            mOnDetachCleanup = mLauncher.getAppWidgetHolder()
+                    .addOnUpdateListener(mInfo.appWidgetId, mAppwidget, this::checkIfRestored);
+            checkIfRestored();
+        }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        if (mOnDetachCleanup != null) {
+            mOnDetachCleanup.close();
+            mOnDetachCleanup = null;
+        }
+    }
+
+    /**
+     * Forces the Launcher to reinflate the widget view
+     */
+    public void reInflate() {
+        if (!isAttachedToWindow()) {
+            return;
+        }
+        LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) getTag();
+        if (info == null) {
+            // This occurs when LauncherAppWidgetHostView is used to render a preview layout.
+            return;
+        }
+        // Remove and rebind the current widget (which was inflated in the wrong
+        // orientation), but don't delete it from the database
+        mLauncher.removeItem(this, info, false  /* deleteFromDb */,
+                "widget removed because of configuration change");
+        mLauncher.bindAppWidget(info);
+    }
+
+    @Override
     public void updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth,
             int maxHeight) {
         // No-op
@@ -147,7 +234,10 @@
             mCenterDrawable.setCallback(null);
             mCenterDrawable = null;
         }
+        mDragFlags = 0;
         if (info.bitmap.icon != null) {
+            mDragFlags = FLAG_DRAW_ICON;
+
             Drawable widgetCategoryIcon = getWidgetCategoryIcon();
             // The view displays three modes,
             //   1) App icon in the center
@@ -169,6 +259,8 @@
                         : widgetCategoryIcon;
                 mSettingIconDrawable = getResources().getDrawable(R.drawable.ic_setting).mutate();
                 updateSettingColor(info.bitmap.color);
+
+                mDragFlags |= FLAG_DRAW_SETTINGS | FLAG_DRAW_LABEL;
             } else {
                 mCenterDrawable = widgetCategoryIcon == null
                         ? newPendingIcon(getContext(), info)
@@ -239,68 +331,63 @@
         int availableWidth = getWidth() - paddingLeft - paddingRight - 2 * minPadding;
         int availableHeight = getHeight() - paddingTop - paddingBottom - 2 * minPadding;
 
-        if (mSettingIconDrawable == null) {
-            int maxSize = grid.iconSizePx;
-            int size = Math.min(maxSize, Math.min(availableWidth, availableHeight));
+        float iconSize = ((mDragFlags & FLAG_DRAW_ICON) == 0) ? 0
+                : Math.max(0, Math.min(availableWidth, availableHeight));
+        // Use twice the setting size factor, as the setting is drawn at a corner and the
+        // icon is drawn in the center.
+        float settingIconScaleFactor = ((mDragFlags & FLAG_DRAW_SETTINGS) == 0) ? 0
+                : 1 + SETUP_ICON_SIZE_FACTOR * 2;
 
-            mRect.set(0, 0, size, size);
-            mRect.offsetTo((getWidth() - mRect.width()) / 2, (getHeight() - mRect.height()) / 2);
-            mCenterDrawable.setBounds(mRect);
-        } else  {
-            float iconSize = Math.max(0, Math.min(availableWidth, availableHeight));
+        int maxSize = Math.max(availableWidth, availableHeight);
+        if (iconSize * settingIconScaleFactor > maxSize) {
+            // There is an overlap
+            iconSize = maxSize / settingIconScaleFactor;
+        }
 
-            // Use twice the setting size factor, as the setting is drawn at a corner and the
-            // icon is drawn in the center.
-            float settingIconScaleFactor = 1 + SETUP_ICON_SIZE_FACTOR * 2;
-            int maxSize = Math.max(availableWidth, availableHeight);
-            if (iconSize * settingIconScaleFactor > maxSize) {
-                // There is an overlap
-                iconSize = maxSize / settingIconScaleFactor;
+        int actualIconSize = (int) Math.min(iconSize, grid.iconSizePx);
+
+        // Icon top when we do not draw the text
+        int iconTop = (getHeight() - actualIconSize) / 2;
+        mSetupTextLayout = null;
+
+        if (availableWidth > 0 && !TextUtils.isEmpty(mLabel)
+                && ((mDragFlags & FLAG_DRAW_LABEL) != 0)) {
+            // Recreate the setup text.
+            mSetupTextLayout = new StaticLayout(
+                    mLabel, mPaint, availableWidth, Layout.Alignment.ALIGN_CENTER, 1, 0, true);
+            int textHeight = mSetupTextLayout.getHeight();
+
+            // Extra icon size due to the setting icon
+            float minHeightWithText = textHeight + actualIconSize * settingIconScaleFactor
+                    + grid.iconDrawablePaddingPx;
+
+            if (minHeightWithText < availableHeight) {
+                // We can draw the text as well
+                iconTop = (getHeight() - textHeight
+                        - grid.iconDrawablePaddingPx - actualIconSize) / 2;
+
+            } else {
+                // We can't draw the text. Let the iconTop be same as before.
+                mSetupTextLayout = null;
             }
+        }
 
-            int actualIconSize = (int) Math.min(iconSize, grid.iconSizePx);
+        mRect.set(0, 0, actualIconSize, actualIconSize);
+        mRect.offset((getWidth() - actualIconSize) / 2, iconTop);
+        mCenterDrawable.setBounds(mRect);
 
-            // Icon top when we do not draw the text
-            int iconTop = (getHeight() - actualIconSize) / 2;
-            mSetupTextLayout = null;
-
-            if (availableWidth > 0) {
-                // Recreate the setup text.
-                mSetupTextLayout = new StaticLayout(
-                        getResources().getText(R.string.gadget_complete_setup_text), mPaint,
-                        availableWidth, Layout.Alignment.ALIGN_CENTER, 1, 0, true);
-                int textHeight = mSetupTextLayout.getHeight();
-
-                // Extra icon size due to the setting icon
-                float minHeightWithText = textHeight + actualIconSize * settingIconScaleFactor
-                        + grid.iconDrawablePaddingPx;
-
-                if (minHeightWithText < availableHeight) {
-                    // We can draw the text as well
-                    iconTop = (getHeight() - textHeight -
-                            grid.iconDrawablePaddingPx - actualIconSize) / 2;
-
-                } else {
-                    // We can't draw the text. Let the iconTop be same as before.
-                    mSetupTextLayout = null;
-                }
-            }
-
-            mRect.set(0, 0, actualIconSize, actualIconSize);
-            mRect.offset((getWidth() - actualIconSize) / 2, iconTop);
-            mCenterDrawable.setBounds(mRect);
-
+        if (mSettingIconDrawable != null) {
             mRect.left = paddingLeft + minPadding;
             mRect.right = mRect.left + (int) (SETUP_ICON_SIZE_FACTOR * actualIconSize);
             mRect.top = paddingTop + minPadding;
             mRect.bottom = mRect.top + (int) (SETUP_ICON_SIZE_FACTOR * actualIconSize);
             mSettingIconDrawable.setBounds(mRect);
+        }
 
-            if (mSetupTextLayout != null) {
-                // Set up position for dragging the text
-                mRect.left = paddingLeft + minPadding;
-                mRect.top = mCenterDrawable.getBounds().bottom + grid.iconDrawablePaddingPx;
-            }
+        if (mSetupTextLayout != null) {
+            // Set up position for dragging the text
+            mRect.left = paddingLeft + minPadding;
+            mRect.top = mCenterDrawable.getBounds().bottom + grid.iconDrawablePaddingPx;
         }
     }
 
diff --git a/src/com/android/launcher3/widget/WidgetHostViewLoader.java b/src/com/android/launcher3/widget/WidgetHostViewLoader.java
index b18cd47..1cc00ef 100644
--- a/src/com/android/launcher3/widget/WidgetHostViewLoader.java
+++ b/src/com/android/launcher3/widget/WidgetHostViewLoader.java
@@ -1,7 +1,6 @@
 package com.android.launcher3.widget;
 
 import android.appwidget.AppWidgetHostView;
-import android.content.Context;
 import android.os.Bundle;
 import android.os.Handler;
 import android.util.Log;
@@ -117,7 +116,7 @@
                     return;
                 }
                 AppWidgetHostView hostView = mLauncher.getAppWidgetHolder().createView(
-                        (Context) mLauncher, mWidgetLoadingId, pInfo);
+                        mWidgetLoadingId, pInfo);
                 mInfo.boundWidget = hostView;
 
                 // We used up the widget Id in binding the above view.
diff --git a/src/com/android/launcher3/widget/WidgetInflater.kt b/src/com/android/launcher3/widget/WidgetInflater.kt
new file mode 100644
index 0000000..00707f5
--- /dev/null
+++ b/src/com/android/launcher3/widget/WidgetInflater.kt
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2024 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
+
+import android.content.Context
+import com.android.launcher3.Launcher
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.logging.FileLog
+import com.android.launcher3.model.WidgetsModel
+import com.android.launcher3.model.data.LauncherAppWidgetInfo
+import com.android.launcher3.qsb.QsbContainerView
+
+/** Utility class for handling widget inflation taking into account all the restore state updates */
+class WidgetInflater(private val context: Context) {
+
+    private val widgetHelper = WidgetManagerHelper(context)
+
+    fun inflateAppWidget(
+        item: LauncherAppWidgetInfo,
+    ): InflationResult {
+        if (item.hasOptionFlag(LauncherAppWidgetInfo.OPTION_SEARCH_WIDGET)) {
+            item.providerName = QsbContainerView.getSearchComponentName(context)
+            if (item.providerName == null) {
+                return InflationResult(
+                    TYPE_DELETE,
+                    reason = "search widget removed because search component cannot be found"
+                )
+            }
+        }
+        if (LauncherAppState.INSTANCE.get(context).isSafeModeEnabled) {
+            return InflationResult(TYPE_PENDING)
+        }
+        val appWidgetInfo: LauncherAppWidgetProviderInfo?
+        var removalReason = ""
+        if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) {
+            // If the provider is not ready, bind as a pending widget.
+            appWidgetInfo = null
+            removalReason = "the provider isn't ready."
+        } else if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
+            // The widget id is not valid. Try to find the widget based on the provider info.
+            appWidgetInfo = widgetHelper.findProvider(item.providerName, item.user)
+            if (appWidgetInfo == null) {
+                if (WidgetsModel.GO_DISABLE_WIDGETS) {
+                    removalReason = "widgets are disabled on go device."
+                } else {
+                    removalReason = "WidgetManagerHelper cannot find a provider from provider info."
+                }
+            }
+        } else {
+            appWidgetInfo =
+                widgetHelper.getLauncherAppWidgetInfo(item.appWidgetId, item.targetComponent)
+            if (appWidgetInfo == null) {
+                if (item.appWidgetId <= LauncherAppWidgetInfo.CUSTOM_WIDGET_ID) {
+                    removalReason = "CustomWidgetManager cannot find provider from that widget id."
+                } else {
+                    removalReason =
+                        ("AppWidgetManager cannot find provider for that widget id." +
+                            " It could be because AppWidgetService is not available, or the" +
+                            " appWidgetId has not been bound to a the provider yet, or you" +
+                            " don't have access to that appWidgetId.")
+                }
+            }
+        }
+
+        var update = false
+
+        // If the provider is ready, but the width is not yet restored, try to restore it.
+        if (
+            !item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) &&
+                item.restoreStatus != LauncherAppWidgetInfo.RESTORE_COMPLETED
+        ) {
+            if (appWidgetInfo == null) {
+                return InflationResult(
+                    type = TYPE_DELETE,
+                    reason =
+                        "Removing restored widget: id=${item.appWidgetId} belongs to component" +
+                            " ${item.providerName} user ${item.user}" +
+                            ", as the provider is null and $removalReason"
+                )
+            }
+
+            // If we do not have a valid id, try to bind an id.
+            if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
+                if (!item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_ALLOCATED)) {
+                    // Id has not been allocated yet. Allocate a new id.
+                    LauncherWidgetHolder.newInstance(context).let {
+                        item.appWidgetId = it.allocateAppWidgetId()
+                        it.destroy()
+                    }
+                    item.restoreStatus =
+                        item.restoreStatus or LauncherAppWidgetInfo.FLAG_ID_ALLOCATED
+
+                    // Also try to bind the widget. If the bind fails, the user will be shown
+                    // a click to setup UI, which will ask for the bind permission.
+                    val pendingInfo = PendingAddWidgetInfo(appWidgetInfo, item.sourceContainer)
+                    pendingInfo.spanX = item.spanX
+                    pendingInfo.spanY = item.spanY
+                    pendingInfo.minSpanX = item.minSpanX
+                    pendingInfo.minSpanY = item.minSpanY
+                    var options = pendingInfo.getDefaultSizeOptions(context)
+                    val isDirectConfig =
+                        item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG)
+                    if (isDirectConfig && item.bindOptions != null) {
+                        val newOptions = item.bindOptions.extras
+                        if (options != null) {
+                            newOptions!!.putAll(options)
+                        }
+                        options = newOptions
+                    }
+                    val success =
+                        widgetHelper.bindAppWidgetIdIfAllowed(
+                            item.appWidgetId,
+                            appWidgetInfo,
+                            options
+                        )
+
+                    // We tried to bind once. If we were not able to bind, we would need to
+                    // go through the permission dialog, which means we cannot skip the config
+                    // activity.
+                    item.bindOptions = null
+                    item.restoreStatus =
+                        item.restoreStatus and LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG.inv()
+
+                    // Bind succeeded
+                    if (success) {
+                        // If the widget has a configure activity, it is still needs to set it
+                        // up, otherwise the widget is ready to go.
+                        item.restoreStatus =
+                            if ((appWidgetInfo.configure == null) || isDirectConfig)
+                                LauncherAppWidgetInfo.RESTORE_COMPLETED
+                            else LauncherAppWidgetInfo.FLAG_UI_NOT_READY
+                    }
+                    update = true
+                }
+            } else if (
+                (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_UI_NOT_READY) &&
+                    (appWidgetInfo.configure == null))
+            ) {
+                // The widget was marked as UI not ready, but there is no configure activity to
+                // update the UI.
+                item.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED
+                update = true
+            } else if (
+                (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_UI_NOT_READY) &&
+                    appWidgetInfo.configure != null)
+            ) {
+                if (widgetHelper.isAppWidgetRestored(item.appWidgetId)) {
+                    item.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED
+                    update = true
+                }
+            }
+        }
+
+        if (item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) {
+            // Verify that we own the widget
+            if (appWidgetInfo == null) {
+                FileLog.e(Launcher.TAG, "Removing invalid widget: id=" + item.appWidgetId)
+                return InflationResult(TYPE_DELETE, reason = removalReason)
+            }
+            item.minSpanX = appWidgetInfo.minSpanX
+            item.minSpanY = appWidgetInfo.minSpanY
+            return InflationResult(TYPE_REAL, isUpdate = update, widgetInfo = appWidgetInfo)
+        } else {
+            return InflationResult(TYPE_PENDING, isUpdate = update, widgetInfo = appWidgetInfo)
+        }
+    }
+
+    data class InflationResult(
+        val type: Int,
+        val reason: String? = null,
+        val isUpdate: Boolean = false,
+        val widgetInfo: LauncherAppWidgetProviderInfo? = null
+    )
+
+    companion object {
+        const val TYPE_DELETE = 0
+
+        const val TYPE_PENDING = 1
+
+        const val TYPE_REAL = 2
+    }
+}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
index fe5c1fd..b9f9ac5 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -19,9 +19,11 @@
 import android.app.ActivityOptions;
 import android.app.Person;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.ShortcutInfo;
 import android.graphics.drawable.ColorDrawable;
+import android.net.Uri;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.ArrayMap;
@@ -29,6 +31,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.UserIconInfo;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -80,6 +83,30 @@
         return users;
     }
 
+    /**
+     * Returns the list of the system packages that are installed at user creation.
+     * An empty list denotes that all system packages are installed for that user at creation.
+     */
+    public static List<String> getPreInstalledSystemPackages(Context context, UserHandle user) {
+        return new ArrayList<>();
+    }
+
+    /**
+     * Returns an intent which can be used to start the App Market activity (Installer
+     * Activity).
+     */
+    public static Intent getAppMarketActivityIntent(Context context, String packageName,
+            UserHandle user) {
+        return new Intent(Intent.ACTION_VIEW)
+                .setData(new Uri.Builder()
+                        .scheme("market")
+                        .authority("details")
+                        .appendQueryParameter("id", packageName)
+                        .build())
+                .putExtra(Intent.EXTRA_REFERRER, new Uri.Builder().scheme("android-app")
+                        .authority(context.getPackageName()).build());
+    }
+
     private static class NoopDrawable extends ColorDrawable {
         @Override
         public int getIntrinsicHeight() {
diff --git a/tests/Android.bp b/tests/Android.bp
index 84c3951..dd0ba9e 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -66,14 +66,14 @@
 filegroup {
     name: "launcher-oop-tests-src",
     srcs: [
-      "src/com/android/launcher3/allapps/TaplOpenCloseAllApps.java",
-      "src/com/android/launcher3/allapps/TaplTestsAllAppsIconsWorking.java",
+      "src/com/android/launcher3/allapps/TaplOpenCloseAllAppsTest.java",
+      "src/com/android/launcher3/allapps/TaplAllAppsIconsWorkingTest.java",
       "src/com/android/launcher3/appiconmenu/TaplAppIconMenuTest.java",
       "src/com/android/launcher3/dragging/TaplDragTest.java",
-      "src/com/android/launcher3/dragging/TaplUninstallRemove.java",
+      "src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java",
       "src/com/android/launcher3/ui/AbstractLauncherUiTest.java",
       "src/com/android/launcher3/ui/PortraitLandscapeRunner.java",
-      "src/com/android/launcher3/ui/TaplTestsLauncher3.java",
+      "src/com/android/launcher3/ui/TaplTestsLauncher3Test.java",
       "src/com/android/launcher3/ui/widget/TaplWidgetPickerTest.java",
       "src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java",
       "src/com/android/launcher3/util/LauncherLayoutBuilder.java",
@@ -174,3 +174,77 @@
     sdk_version: "current",
     min_sdk_version: min_launcher3_sdk_version,
  }
+
+filegroup {
+    name: "launcher-testing-helpers",
+    srcs: [
+      "src/**/*.java",
+      "src/**/*.kt",
+      "src/com/android/launcher3/ui/AbstractLauncherUiTest.java",
+      "tapl/com/android/launcher3/tapl/*.java",
+      "tapl/com/android/launcher3/tapl/*.kt",
+    ],
+    exclude_srcs: [
+        // Test classes
+        "src/**/*Test.java",
+        "src/**/*Test.kt",
+    ],
+}
+
+android_library {
+    name: "Launcher3Lib",
+    srcs: [
+        ":launcher-src",
+        ":launcher-src_shortcuts_overrides",
+        ":launcher-src_ui_overrides",
+    ],
+    static_libs: [
+        "Launcher3CommonDepsLib",
+    ],
+}
+
+android_robolectric_test {
+    enabled: true,
+    name: "Launcher3RoboTests",
+    srcs: [
+        "src/com/android/launcher3/util/*.java",
+        "src/com/android/launcher3/util/*.kt",
+
+        // Test util classes
+        ":launcher-testing-helpers",
+        ":launcher-testing-shared",
+    ],
+    exclude_srcs: [
+        "src/com/android/launcher3/util/CellContentDimensionsTest.kt", // Failing - b/316553889
+
+        // requires modification to work with inline mock maker
+        "src/com/android/launcher3/util/rule/StaticMockitoRule.java",
+
+        // requires kotlin mockito
+        "src/com/android/launcher3/util/LockedUserStateTest.kt",
+        "src/com/android/launcher3/util/DisplayControllerTest.kt",
+    ],
+    java_resource_dirs: ["config"],
+    static_libs: [
+        "flag-junit-base",
+        "com_android_launcher3_flags_lib",
+        "com_android_wm_shell_flags_lib",
+        "androidx.test.uiautomator_uiautomator",
+        "androidx.core_core-animation-testing",
+        "androidx.test.ext.junit",
+        "inline-mockito-robolectric-prebuilt",
+        "platform-parametric-runner-lib",
+        "testables",
+        "Launcher3TestResources",
+        "SystemUISharedLib",
+        "launcher-testing-shared",
+    ],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+        "android.test.mock",
+        "truth",
+    ],
+    instrumentation_for: "Launcher3",
+    upstream: true,
+}
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
index bd9da0a..e5e3354 100644
--- a/tests/AndroidManifest-common.xml
+++ b/tests/AndroidManifest-common.xml
@@ -149,7 +149,8 @@
             android:name="com.android.launcher3.testcomponent.BaseTestingActivity"
             android:label="LauncherTestApp"
             android:exported="true"
-            android:taskAffinity="com.android.launcher3.testcomponent.Affinity1">
+            android:taskAffinity="com.android.launcher3.testcomponent.Affinity1"
+            android:theme="@style/Theme.TestActivities">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
@@ -343,6 +344,24 @@
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity-alias>
+        <activity-alias android:name="AAAActivity"
+            android:label="AAA"
+            android:exported="true"
+            android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity-alias>
+        <activity-alias android:name="ZZZActivity"
+            android:label="ZZZ"
+            android:exported="true"
+            android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity-alias>
 
         <!-- [b/197780098] Disable eager initialization of Jetpack libraries. -->
         <provider
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 5cf96c8..2596b75 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -23,6 +23,14 @@
 
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner" />
+
+        <receiver android:name="com.android.launcher3.compat.PromiseIconUiTest$UnarchiveBroadcastReceiver"
+                  android:enabled="true"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.UNARCHIVE_PACKAGE"/>
+            </intent-filter>
+        </receiver>
     </application>
 
     <instrumentation
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt
index 1781673..92caf23 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt
@@ -55,7 +55,7 @@
 	bottomSheetCloseDuration: 500
 	bottomSheetWorkspaceScale: 0.97
 	bottomSheetDepth: 0.0
-	allAppsShiftRange: 1496.0px (748.0dp)
+	allAppsShiftRange: 1600.0px (800.0dp)
 	allAppsOpenDuration: 500
 	allAppsCloseDuration: 500
 	allAppsIconSizePx: 120.0px (60.0dp)
@@ -87,7 +87,7 @@
 	numShownHotseatIcons: 6
 	hotseatBorderSpace: 100.0px (50.0dp)
 	isQsbInline: false
-	hotseatQsbWidth: 1224.0px (612.0dp)
+	hotseatQsbWidth: 1214.0px (607.0dp)
 	isTaskbarPresent:false
 	isTaskbarPresentInApps:true
 	taskbarHeight: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape3Button.txt
index bd9e267..3815fa9 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape3Button.txt
@@ -55,7 +55,7 @@
 	bottomSheetCloseDuration: 500
 	bottomSheetWorkspaceScale: 0.97
 	bottomSheetDepth: 0.0
-	allAppsShiftRange: 1496.0px (748.0dp)
+	allAppsShiftRange: 1600.0px (800.0dp)
 	allAppsOpenDuration: 500
 	allAppsCloseDuration: 500
 	allAppsIconSizePx: 120.0px (60.0dp)
@@ -87,7 +87,7 @@
 	numShownHotseatIcons: 6
 	hotseatBorderSpace: 100.0px (50.0dp)
 	isQsbInline: false
-	hotseatQsbWidth: 1224.0px (612.0dp)
+	hotseatQsbWidth: 1214.0px (607.0dp)
 	isTaskbarPresent:false
 	isTaskbarPresentInApps:true
 	taskbarHeight: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait.txt
index e983ef7..7e0f316 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait.txt
@@ -55,7 +55,7 @@
 	bottomSheetCloseDuration: 500
 	bottomSheetWorkspaceScale: 0.97
 	bottomSheetDepth: 0.0
-	allAppsShiftRange: 2019.0px (1009.5dp)
+	allAppsShiftRange: 2560.0px (1280.0dp)
 	allAppsOpenDuration: 500
 	allAppsCloseDuration: 500
 	allAppsIconSizePx: 120.0px (60.0dp)
@@ -66,7 +66,7 @@
 	allAppsBorderSpacePxX: 16.0px (8.0dp)
 	allAppsBorderSpacePxY: 32.0px (16.0dp)
 	numShownAllAppsColumns: 6
-	allAppsPadding.top: 541.0px (270.5dp)
+	allAppsPadding.top: 104.0px (52.0dp)
 	allAppsPadding.left: 32.0px (16.0dp)
 	allAppsPadding.right: 32.0px (16.0dp)
 	allAppsLeftRightMargin: 152.0px (76.0dp)
@@ -87,7 +87,7 @@
 	numShownHotseatIcons: 6
 	hotseatBorderSpace: 116.0px (58.0dp)
 	isQsbInline: false
-	hotseatQsbWidth: 1300.0px (650.0dp)
+	hotseatQsbWidth: 1290.0px (645.0dp)
 	isTaskbarPresent:false
 	isTaskbarPresentInApps:true
 	taskbarHeight: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait3Button.txt
index aa92838..58c3890 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait3Button.txt
@@ -55,7 +55,7 @@
 	bottomSheetCloseDuration: 500
 	bottomSheetWorkspaceScale: 0.97
 	bottomSheetDepth: 0.0
-	allAppsShiftRange: 2019.0px (1009.5dp)
+	allAppsShiftRange: 2560.0px (1280.0dp)
 	allAppsOpenDuration: 500
 	allAppsCloseDuration: 500
 	allAppsIconSizePx: 120.0px (60.0dp)
@@ -66,7 +66,7 @@
 	allAppsBorderSpacePxX: 16.0px (8.0dp)
 	allAppsBorderSpacePxY: 32.0px (16.0dp)
 	numShownAllAppsColumns: 6
-	allAppsPadding.top: 541.0px (270.5dp)
+	allAppsPadding.top: 104.0px (52.0dp)
 	allAppsPadding.left: 32.0px (16.0dp)
 	allAppsPadding.right: 32.0px (16.0dp)
 	allAppsLeftRightMargin: 152.0px (76.0dp)
@@ -87,7 +87,7 @@
 	numShownHotseatIcons: 6
 	hotseatBorderSpace: 116.0px (58.0dp)
 	isQsbInline: false
-	hotseatQsbWidth: 1300.0px (650.0dp)
+	hotseatQsbWidth: 1290.0px (645.0dp)
 	isTaskbarPresent:false
 	isTaskbarPresentInApps:true
 	taskbarHeight: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape.txt
index 43e4a60..1e363a2 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape.txt
@@ -55,7 +55,7 @@
 	bottomSheetCloseDuration: 500
 	bottomSheetWorkspaceScale: 0.97
 	bottomSheetDepth: 1.0
-	allAppsShiftRange: 1730.0px (659.0476dp)
+	allAppsShiftRange: 1840.0px (700.9524dp)
 	allAppsOpenDuration: 500
 	allAppsCloseDuration: 500
 	allAppsIconSizePx: 141.0px (53.714287dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button.txt
index e7ea839..617b54b 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button.txt
@@ -55,7 +55,7 @@
 	bottomSheetCloseDuration: 500
 	bottomSheetWorkspaceScale: 0.97
 	bottomSheetDepth: 1.0
-	allAppsShiftRange: 1730.0px (659.0476dp)
+	allAppsShiftRange: 1840.0px (700.9524dp)
 	allAppsOpenDuration: 500
 	allAppsCloseDuration: 500
 	allAppsIconSizePx: 141.0px (53.714287dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait.txt
index 043380c..483b5e7 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait.txt
@@ -55,7 +55,7 @@
 	bottomSheetCloseDuration: 500
 	bottomSheetWorkspaceScale: 0.97
 	bottomSheetDepth: 1.0
-	allAppsShiftRange: 2075.0px (790.4762dp)
+	allAppsShiftRange: 2208.0px (841.1429dp)
 	allAppsOpenDuration: 500
 	allAppsCloseDuration: 500
 	allAppsIconSizePx: 141.0px (53.714287dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button.txt
index a1b3e95..8d0640c 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button.txt
@@ -55,7 +55,7 @@
 	bottomSheetCloseDuration: 500
 	bottomSheetWorkspaceScale: 0.97
 	bottomSheetDepth: 1.0
-	allAppsShiftRange: 2075.0px (790.4762dp)
+	allAppsShiftRange: 2208.0px (841.1429dp)
 	allAppsOpenDuration: 500
 	allAppsCloseDuration: 500
 	allAppsIconSizePx: 141.0px (53.714287dp)
diff --git a/tests/config/robolectric.properties b/tests/config/robolectric.properties
new file mode 100644
index 0000000..fab7251
--- /dev/null
+++ b/tests/config/robolectric.properties
@@ -0,0 +1 @@
+sdk=NEWEST_SDK
diff --git a/tests/res/values/styles.xml b/tests/res/values/styles.xml
new file mode 100644
index 0000000..1e1a2cd
--- /dev/null
+++ b/tests/res/values/styles.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2024 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.
+  -->
+
+<resources>
+    <style name="Theme.TestActivities" parent="@android:style/Theme.DeviceDefault.DayNight">
+        <!-- Hardcoded ActionBar height to avoid changes while emulating -->
+        <!-- (56dp - default for handheld) -->
+        <item name="android:actionBarSize">168px</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/tests/res/xml/invalid_responsive_spec_1.xml b/tests/res/xml/invalid_responsive_spec_1.xml
index d1bcf65..7845d1e 100644
--- a/tests/res/xml/invalid_responsive_spec_1.xml
+++ b/tests/res/xml/invalid_responsive_spec_1.xml
@@ -27,7 +27,7 @@
     <workspaceSpec
         launcher:maxAvailableSize="9999dp"
         launcher:dimensionType="width">
-        <cellSize launcher:ofRemainderSpace="0.25" />
+        <cellSize launcher:ofRemainderSpace="1" />
         <endPadding launcher:fixedSize="22dp" />
         <gutter launcher:fixedSize="16dp" />
         <startPadding launcher:fixedSize="22dp" />
diff --git a/tests/res/xml/invalid_responsive_spec_2.xml b/tests/res/xml/invalid_responsive_spec_2.xml
index 49e1f93..ae30bb9 100644
--- a/tests/res/xml/invalid_responsive_spec_2.xml
+++ b/tests/res/xml/invalid_responsive_spec_2.xml
@@ -28,7 +28,7 @@
         <workspaceSpec
             launcher:maxAvailableSize="9999dp"
             launcher:dimensionType="width">
-            <cellSize launcher:ofRemainderSpace="0.25" />
+            <cellSize launcher:ofRemainderSpace="1" />
             <endPadding launcher:fixedSize="22dp" />
             <gutter launcher:fixedSize="16dp" />
             <startPadding launcher:fixedSize="22dp" />
@@ -38,7 +38,7 @@
     <workspaceSpec
         launcher:maxAvailableSize="9999dp"
         launcher:dimensionType="width">
-        <cellSize launcher:ofRemainderSpace="0.25" />
+        <cellSize launcher:ofRemainderSpace="1" />
         <endPadding launcher:fixedSize="22dp" />
         <gutter launcher:fixedSize="16dp" />
         <startPadding launcher:fixedSize="22dp" />
diff --git a/tests/res/xml/valid_responsive_spec_unsorted.xml b/tests/res/xml/valid_responsive_spec_unsorted.xml
index 9a463d5..8676f48 100644
--- a/tests/res/xml/valid_responsive_spec_unsorted.xml
+++ b/tests/res/xml/valid_responsive_spec_unsorted.xml
@@ -22,7 +22,7 @@
             <startPadding launcher:fixedSize="0dp" />
             <endPadding launcher:fixedSize="34dp" />
             <gutter launcher:fixedSize="12dp" />
-            <cellSize launcher:ofRemainderSpace="0.25" />
+            <cellSize launcher:ofRemainderSpace="1" />
         </workspaceSpec>
 
         <!-- Height spec -->
@@ -32,7 +32,7 @@
             <startPadding launcher:fixedSize="0dp" />
             <endPadding launcher:fixedSize="24dp" />
             <gutter launcher:fixedSize="12dp" />
-            <cellSize launcher:ofRemainderSpace="0.25" />
+            <cellSize launcher:ofRemainderSpace="1" />
         </workspaceSpec>
 
         <!-- Width spec -->
@@ -42,7 +42,7 @@
             <startPadding launcher:fixedSize="16dp" />
             <endPadding launcher:fixedSize="64dp" />
             <gutter launcher:fixedSize="12dp" />
-            <cellSize launcher:ofRemainderSpace="0.25" />
+            <cellSize launcher:ofRemainderSpace="1" />
         </workspaceSpec>
         <workspaceSpec
             launcher:dimensionType="width"
@@ -50,7 +50,7 @@
             <startPadding launcher:fixedSize="36dp" />
             <endPadding launcher:fixedSize="80dp" />
             <gutter launcher:fixedSize="12dp" />
-            <cellSize launcher:ofRemainderSpace="0.25" />
+            <cellSize launcher:ofRemainderSpace="1" />
         </workspaceSpec>
         <workspaceSpec
             launcher:dimensionType="width"
@@ -58,7 +58,7 @@
             <startPadding launcher:fixedSize="0dp" />
             <endPadding launcher:fixedSize="36dp" />
             <gutter launcher:fixedSize="12dp" />
-            <cellSize launcher:ofRemainderSpace="0.25" />
+            <cellSize launcher:ofRemainderSpace="1" />
         </workspaceSpec>
     </specs>
 
@@ -71,7 +71,7 @@
             <startPadding launcher:fixedSize="2dp" />
             <endPadding launcher:fixedSize="2dp" />
             <gutter launcher:fixedSize="8dp" />
-            <cellSize launcher:ofRemainderSpace="0.25" />
+            <cellSize launcher:ofRemainderSpace="1" />
         </workspaceSpec>
 
         <!-- Width spec -->
@@ -81,7 +81,7 @@
             <startPadding launcher:fixedSize="1dp" />
             <endPadding launcher:fixedSize="1dp" />
             <gutter launcher:fixedSize="8dp" />
-            <cellSize launcher:ofRemainderSpace="0.25" />
+            <cellSize launcher:ofRemainderSpace="1" />
         </workspaceSpec>
     </specs>
 
@@ -122,7 +122,7 @@
             <startPadding launcher:fixedSize="22dp" />
             <endPadding launcher:fixedSize="22dp" />
             <gutter launcher:fixedSize="16dp" />
-            <cellSize launcher:ofRemainderSpace="0.25" />
+            <cellSize launcher:ofRemainderSpace="1" />
         </workspaceSpec>
     </specs>
 </workspaceSpecs>
diff --git a/tests/res/xml/valid_workspace_file.xml b/tests/res/xml/valid_workspace_file.xml
index 9c44502..dc9963a 100644
--- a/tests/res/xml/valid_workspace_file.xml
+++ b/tests/res/xml/valid_workspace_file.xml
@@ -54,7 +54,7 @@
             <startPadding launcher:fixedSize="22dp" />
             <endPadding launcher:fixedSize="22dp" />
             <gutter launcher:fixedSize="16dp" />
-            <cellSize launcher:ofRemainderSpace="0.25" />
+            <cellSize launcher:ofRemainderSpace="1" />
         </workspaceSpec>
     </specs>
 
@@ -67,7 +67,7 @@
             <startPadding launcher:fixedSize="0dp" />
             <endPadding launcher:fixedSize="24dp" />
             <gutter launcher:fixedSize="12dp" />
-            <cellSize launcher:ofRemainderSpace="0.25" />
+            <cellSize launcher:ofRemainderSpace="1" />
         </workspaceSpec>
         <workspaceSpec
             launcher:dimensionType="height"
@@ -75,7 +75,7 @@
             <startPadding launcher:fixedSize="0dp" />
             <endPadding launcher:fixedSize="34dp" />
             <gutter launcher:fixedSize="12dp" />
-            <cellSize launcher:ofRemainderSpace="0.25" />
+            <cellSize launcher:ofRemainderSpace="1" />
         </workspaceSpec>
 
         <!-- Width spec -->
@@ -85,7 +85,7 @@
             <startPadding launcher:fixedSize="0dp" />
             <endPadding launcher:fixedSize="36dp" />
             <gutter launcher:fixedSize="12dp" />
-            <cellSize launcher:ofRemainderSpace="0.25" />
+            <cellSize launcher:ofRemainderSpace="1" />
         </workspaceSpec>
         <workspaceSpec
             launcher:dimensionType="width"
@@ -93,7 +93,7 @@
             <startPadding launcher:fixedSize="16dp" />
             <endPadding launcher:fixedSize="64dp" />
             <gutter launcher:fixedSize="12dp" />
-            <cellSize launcher:ofRemainderSpace="0.25" />
+            <cellSize launcher:ofRemainderSpace="1" />
         </workspaceSpec>
         <workspaceSpec
             launcher:dimensionType="width"
@@ -101,7 +101,7 @@
             <startPadding launcher:fixedSize="36dp" />
             <endPadding launcher:fixedSize="80dp" />
             <gutter launcher:fixedSize="12dp" />
-            <cellSize launcher:ofRemainderSpace="0.25" />
+            <cellSize launcher:ofRemainderSpace="1" />
         </workspaceSpec>
     </specs>
 </workspaceSpecs>
diff --git a/tests/res/xml/valid_workspace_unsorted_file.xml b/tests/res/xml/valid_workspace_unsorted_file.xml
index 6bf7c78..f783e9f 100644
--- a/tests/res/xml/valid_workspace_unsorted_file.xml
+++ b/tests/res/xml/valid_workspace_unsorted_file.xml
@@ -52,7 +52,7 @@
             <startPadding launcher:fixedSize="22dp" />
             <endPadding launcher:fixedSize="22dp" />
             <gutter launcher:fixedSize="16dp" />
-            <cellSize launcher:ofRemainderSpace="0.25" />
+            <cellSize launcher:ofRemainderSpace="1" />
         </workspaceSpec>
     </specs>
 </workspaceSpecs>
diff --git a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
index fcb5158..3e188e6 100644
--- a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
+++ b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
@@ -107,7 +107,7 @@
     public static final String REQUEST_WIDGETS_SCROLL_Y = "widgets-scroll-y";
     public static final String REQUEST_TARGET_INSETS = "target-insets";
     public static final String REQUEST_WINDOW_INSETS = "window-insets";
-    public static final String REQUEST_IME_INSETS = "ime-insets";
+    public static final String REQUEST_SYSTEM_GESTURE_REGION = "gesture-region";
     public static final String REQUEST_PID = "pid";
     public static final String REQUEST_FORCE_GC = "gc";
     public static final String REQUEST_RECENT_TASKS_LIST = "recent-tasks-list";
@@ -147,6 +147,8 @@
     public static final String REQUEST_GET_GRID_TASK_SIZE_RECT_FOR_TABLET =
             "get-grid-task-size-rect-for-tablet";
     public static final String REQUEST_GET_OVERVIEW_PAGE_SPACING = "get-overview-page-spacing";
+    public static final String REQUEST_GET_OVERVIEW_CURRENT_PAGE_INDEX =
+            "get-overview-current-page-index";
     public static final String REQUEST_ENABLE_ROTATION = "enable_rotation";
     public static final String REQUEST_ENABLE_SUGGESTION = "enable-suggestion";
     public static final String REQUEST_MODEL_QUEUE_CLEARED = "model-queue-cleared";
@@ -172,6 +174,9 @@
 
     public static final String REQUEST_FLAG_ENABLE_GRID_ONLY_OVERVIEW = "enable-grid-only-overview";
 
+    public static final String REQUEST_UNSTASH_BUBBLE_BAR_IF_STASHED =
+            "unstash-bubble-bar-if-stashed";
+
     /** Logs {@link Log#d(String, String)} if {@link #sDebugTracing} is true. */
     public static void testLogD(String tag, String message) {
         if (!sDebugTracing) {
diff --git a/tests/src/com/android/launcher3/allapps/AlphabeticalAppsListTest.java b/tests/src/com/android/launcher3/allapps/AlphabeticalAppsListTest.java
index 259f519..3068785 100644
--- a/tests/src/com/android/launcher3/allapps/AlphabeticalAppsListTest.java
+++ b/tests/src/com/android/launcher3/allapps/AlphabeticalAppsListTest.java
@@ -19,6 +19,9 @@
 import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
 
 import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_PRIVATE_SPACE_HEADER;
+import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_BOTTOM_LEFT;
+import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_BOTTOM_RIGHT;
+import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_NOTHING;
 import static com.android.launcher3.allapps.UserProfileManager.STATE_DISABLED;
 import static com.android.launcher3.allapps.UserProfileManager.STATE_ENABLED;
 import static com.android.launcher3.allapps.UserProfileManager.STATE_TRANSITION;
@@ -61,6 +64,8 @@
     private static final int PRIVATE_SPACE_HEADER_ITEM_COUNT = 1;
     private static final int MAIN_USER_APP_COUNT = 2;
     private static final int PRIVATE_USER_APP_COUNT = 1;
+    private static final int NUM_APP_COLS = 4;
+    private static final int NUM_APP_ROWS = 3;
 
     private AlphabeticalAppsList<?> mAlphabeticalAppsList;
     @Mock
@@ -81,6 +86,7 @@
                 info != null && info.user.equals(PRIVATE_HANDLE));
         mAlphabeticalAppsList = new AlphabeticalAppsList<>(mContext, mAllAppsStore,
                 null, mPrivateProfileManager);
+        mAlphabeticalAppsList.setNumAppsPerRowAllApps(NUM_APP_COLS);
     }
 
     @Test
@@ -182,6 +188,94 @@
                 .toList().size());
     }
 
+    @Test
+    public void getRoundRegions_whenIndexIsMiddleOfLastRow_roundNothing() {
+        int index = 3;
+
+        int roundRegions = mAlphabeticalAppsList.getRoundRegions(index,
+                NUM_APP_COLS * NUM_APP_ROWS);
+
+        assertEquals(ROUND_NOTHING, roundRegions);
+    }
+
+    @Test
+    public void getRoundRegions_whenIndexIsInEndOfLastRow_roundBottomRight() {
+        int index = 11;
+
+        int roundRegions = mAlphabeticalAppsList.getRoundRegions(index,
+                NUM_APP_COLS * NUM_APP_ROWS);
+
+        assertEquals(ROUND_BOTTOM_RIGHT, roundRegions);
+    }
+
+    @Test
+    public void getRoundRegions_whenIndexIsInBeginningOfLastRow_roundBottomLeft() {
+        int index = 8;
+
+        int roundRegions = mAlphabeticalAppsList.getRoundRegions(index,
+                NUM_APP_COLS * NUM_APP_ROWS);
+
+        assertEquals(ROUND_BOTTOM_LEFT, roundRegions);
+    }
+
+    @Test
+    public void getRoundRegions_whenIndexIsInMiddleOfLastRow_roundNothing() {
+        int index = 9;
+
+        int roundRegions = mAlphabeticalAppsList.getRoundRegions(index,
+                NUM_APP_COLS * NUM_APP_ROWS);
+
+        assertEquals(ROUND_NOTHING, roundRegions);
+    }
+
+    @Test
+    public void getRoundRegions_whenIndexIsInMiddleRow_roundNothing() {
+        int index = 5;
+
+        int roundRegions = mAlphabeticalAppsList.getRoundRegions(index,
+                NUM_APP_COLS * NUM_APP_ROWS);
+
+        assertEquals(ROUND_NOTHING, roundRegions);
+    }
+
+    @Test
+    public void getRoundRegions_whenIndexIsInBeginningOfTopRow_roundNothing() {
+        int index = 0;
+
+        int roundRegions = mAlphabeticalAppsList.getRoundRegions(index,
+                NUM_APP_COLS * NUM_APP_ROWS);
+
+        assertEquals(ROUND_NOTHING, roundRegions);
+    }
+
+    @Test
+    public void getRoundRegions_whenIndexIsInLastOfTopRow_roundNothing() {
+        int index = 3;
+
+        int roundRegions = mAlphabeticalAppsList.getRoundRegions(index,
+                NUM_APP_COLS * NUM_APP_ROWS);
+
+        assertEquals(ROUND_NOTHING, roundRegions);
+    }
+
+    @Test
+    public void getRoundRegions_whenIndexIsInMiddleOfLastRowLastItem_roundBottomRight() {
+        int index = 9;
+
+        int roundRegions = mAlphabeticalAppsList.getRoundRegions(index, index+1);
+
+        assertEquals(ROUND_BOTTOM_RIGHT, roundRegions);
+    }
+
+    @Test
+    public void getRoundRegions_whenIndexIsInBeginningOfLastRowLastItem_roundBottomRight() {
+        int index = 8;
+
+        int roundRegions = mAlphabeticalAppsList.getRoundRegions(index, index+1);
+
+        assertEquals(ROUND_BOTTOM_RIGHT | ROUND_BOTTOM_LEFT, roundRegions);
+    }
+
     private int addPrivateSpaceHeader(List<BaseAllAppsAdapter.AdapterItem> adapterItemList) {
         adapterItemList.add(new BaseAllAppsAdapter.AdapterItem(VIEW_TYPE_PRIVATE_SPACE_HEADER));
         return adapterItemList.size();
diff --git a/tests/src/com/android/launcher3/allapps/TaplTestsAllAppsIconsWorking.java b/tests/src/com/android/launcher3/allapps/TaplAllAppsIconsWorkingTest.java
similarity index 96%
rename from tests/src/com/android/launcher3/allapps/TaplTestsAllAppsIconsWorking.java
rename to tests/src/com/android/launcher3/allapps/TaplAllAppsIconsWorkingTest.java
index 9f6bbdf..27a2c75 100644
--- a/tests/src/com/android/launcher3/allapps/TaplTestsAllAppsIconsWorking.java
+++ b/tests/src/com/android/launcher3/allapps/TaplAllAppsIconsWorkingTest.java
@@ -33,7 +33,7 @@
  * The test runs in Out of process (Oop) and in process.
  * Makes sure the basic behaviors of Icons on AllApps are working.
  */
-public class TaplTestsAllAppsIconsWorking extends AbstractLauncherUiTest {
+public class TaplAllAppsIconsWorkingTest extends AbstractLauncherUiTest {
 
     @Before
     public void setUp() throws Exception {
diff --git a/tests/src/com/android/launcher3/allapps/TaplOpenCloseAllApps.java b/tests/src/com/android/launcher3/allapps/TaplOpenCloseAllAppsTest.java
similarity index 96%
rename from tests/src/com/android/launcher3/allapps/TaplOpenCloseAllApps.java
rename to tests/src/com/android/launcher3/allapps/TaplOpenCloseAllAppsTest.java
index 4f10287..da0beb1 100644
--- a/tests/src/com/android/launcher3/allapps/TaplOpenCloseAllApps.java
+++ b/tests/src/com/android/launcher3/allapps/TaplOpenCloseAllAppsTest.java
@@ -43,7 +43,7 @@
  * Test that we can open and close the all apps in multiple situations.
  * The test runs in Out of process (Oop) and in process.
  */
-public class TaplOpenCloseAllApps extends AbstractLauncherUiTest {
+public class TaplOpenCloseAllAppsTest extends AbstractLauncherUiTest {
 
     public static final String READ_DEVICE_CONFIG_PERMISSION =
             "android.permission.READ_DEVICE_CONFIG";
@@ -130,6 +130,7 @@
      */
     @Test
     @PortraitLandscape
+    @PlatinumTest(focusArea = "launcher")
     public void testAllAppsFromHome() {
         // Test opening all apps
         assertNotNull("switchToAllApps() returned null",
@@ -181,10 +182,10 @@
             executeOnLauncher(launcher -> assertTrue("flingBackward() didn't scroll App Apps",
                     flingBackwardY < flingForwardY));
 
-            // Test scrolling down to YouTube.
-            assertNotNull("All apps: can't find YouTube", allApps.getAppIcon("YouTube"));
-            // Test scrolling up to Camera.
-            assertNotNull("All apps: can't find Camera", allApps.getAppIcon("Camera"));
+            // Test scrolling down to the end of the app list.
+            assertNotNull("All apps: can't find YouTube", allApps.getAppIcon("ZZZ"));
+            // Test scrolling up to the beginning oof the app list.
+            assertNotNull("All apps: can't find Camera", allApps.getAppIcon("AAA"));
             // Test failing to find a non-existing app.
             final AllApps allAppsFinal = allApps;
             expectFail("All apps: could find a non-existing app",
diff --git a/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java b/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
index fb364ad..dbb2715 100644
--- a/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
+++ b/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
@@ -64,7 +64,7 @@
         runOnExecutorSync(MODEL_EXECUTOR, () -> {
             ModelDbController controller = model.getModelDbController();
             // Migrate any previous data so that the DB state is correct
-            controller.tryMigrateDB();
+            controller.tryMigrateDB(null /* restoreEventLogger */);
 
             // Create DB again to load fresh data
             controller.createEmptyDB();
diff --git a/tests/src/com/android/launcher3/compat/TaplPromiseIconUiTest.java b/tests/src/com/android/launcher3/compat/TaplPromiseIconUiTest.java
index 8200c94..bfa0d34 100644
--- a/tests/src/com/android/launcher3/compat/TaplPromiseIconUiTest.java
+++ b/tests/src/com/android/launcher3/compat/TaplPromiseIconUiTest.java
@@ -15,9 +15,19 @@
  */
 package com.android.launcher3.compat;
 
+import static com.android.launcher3.Flags.FLAG_ENABLE_SUPPORT_FOR_ARCHIVING;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
 import android.content.pm.PackageInstaller.SessionParams;
 import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.text.TextUtils;
 
 import androidx.test.filters.LargeTest;
@@ -27,12 +37,15 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator;
+import com.android.launcher3.util.TestUtil;
 import com.android.launcher3.util.rule.ViewCaptureRule;
 
 import org.junit.After;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.IOException;
 import java.util.UUID;
 
 
@@ -43,6 +56,14 @@
 @RunWith(AndroidJUnit4.class)
 public class TaplPromiseIconUiTest extends AbstractLauncherUiTest {
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    public static final String PACKAGE_NAME = "test.promise.app";
+    public static final String DUMMY_PACKAGE = "com.example.android.aardwolf";
+    public static final String DUMMY_LABEL = "Aardwolf";
+
     private int mSessionId = -1;
 
     @Override
@@ -55,18 +76,19 @@
     }
 
     @After
-    public void tearDown() {
+    public void tearDown() throws IOException {
         if (mSessionId > -1) {
             mTargetContext.getPackageManager().getPackageInstaller().abandonSession(mSessionId);
         }
+        TestUtil.uninstallDummyApp();
     }
 
     /**
      * Create a session and return the id.
      */
-    private int createSession(String label, Bitmap icon) throws Throwable {
+    private int createSession(String packageName, String label, Bitmap icon) throws Throwable {
         SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
-        params.setAppPackageName("test.promise.app");
+        params.setAppPackageName(packageName);
         params.setAppLabel(label);
         params.setAppIcon(icon);
         params.setInstallReason(PackageManager.INSTALL_REASON_USER);
@@ -80,7 +102,8 @@
                 info != null && TextUtils.equals(info.title, appLabel);
 
         // Create and add test session
-        mSessionId = createSession(appLabel, Bitmap.createBitmap(100, 100, Bitmap.Config.ALPHA_8));
+        mSessionId = createSession(PACKAGE_NAME, appLabel,
+                Bitmap.createBitmap(100, 100, Bitmap.Config.ALPHA_8));
 
         // Verify promise icon is added
         waitForLauncherCondition("Test Promise App not found on workspace", launcher ->
@@ -103,7 +126,7 @@
                 info != null && TextUtils.equals(info.title, appLabel);
 
         // Create and add test session without icon or label
-        mSessionId = createSession(null, null);
+        mSessionId = createSession(PACKAGE_NAME, null, null);
 
         // Sleep for duration of animation if a view was to be added + some buffer time.
         Thread.sleep(Launcher.NEW_APPS_PAGE_MOVE_DELAY + Launcher.NEW_APPS_ANIMATION_DELAY + 500);
@@ -112,4 +135,40 @@
         waitForLauncherCondition("Test Promise App not found on workspace", launcher ->
                 launcher.getWorkspace().getFirstMatch(findPromiseApp) == null);
     }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_ENABLE_SUPPORT_FOR_ARCHIVING)
+    public void testPromiseIcon_addedArchivedApp() throws Throwable {
+        installDummyAppAndWaitForUIUpdate();
+        assertThat(mDevice.executeShellCommand(String.format("pm archive %s", DUMMY_PACKAGE)))
+                .isEqualTo("Success\n");
+
+        final ItemOperator findPromiseApp = (info, view) ->
+                info != null && TextUtils.equals(info.title, DUMMY_LABEL);
+
+        // Create and add test session
+        mSessionId = createSession(DUMMY_PACKAGE, /* label= */ "",
+                Bitmap.createBitmap(100, 100, Bitmap.Config.ALPHA_8));
+
+        // Verify promise icon is added
+        waitForLauncherCondition("Test Promise App not found on workspace", launcher ->
+                launcher.getWorkspace().getFirstMatch(findPromiseApp) != null);
+
+        // Remove session
+        mTargetContext.getPackageManager().getPackageInstaller().abandonSession(mSessionId);
+        mSessionId = -1;
+    }
+
+    // Dummy receiver to fulfill archiving platform requirements, unused in reality.
+    public static class UnarchiveBroadcastReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+        }
+    }
+
+    private void installDummyAppAndWaitForUIUpdate() throws IOException {
+        TestUtil.installDummyApp();
+        mLauncher.waitForModelQueueCleared();
+        mLauncher.waitForLauncherInitialized();
+    }
 }
diff --git a/tests/src/com/android/launcher3/dragging/TaplDragTest.java b/tests/src/com/android/launcher3/dragging/TaplDragTest.java
index e040367..94a96aa 100644
--- a/tests/src/com/android/launcher3/dragging/TaplDragTest.java
+++ b/tests/src/com/android/launcher3/dragging/TaplDragTest.java
@@ -20,6 +20,8 @@
 import static com.android.launcher3.util.TestConstants.AppNames.MAPS_APP_NAME;
 import static com.android.launcher3.util.TestConstants.AppNames.STORE_APP_NAME;
 import static com.android.launcher3.ui.AbstractLauncherUiTest.initialize;
+import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
+import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -40,9 +42,9 @@
 import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
 import com.android.launcher3.util.TestUtil;
 import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
+import com.android.launcher3.util.rule.TestStabilityRule;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 
 /**
@@ -71,7 +73,8 @@
     @Test
     @PortraitLandscape
     @ScreenRecord
-    @Ignore // b/233075289
+    // Staging; will be promoted to presubmit if stable
+    @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT)
     @PlatinumTest(focusArea = "launcher")
     public void testDragToFolder() {
         // TODO: add the use case to drag an icon to an existing folder. Currently it either fails
diff --git a/tests/src/com/android/launcher3/dragging/TaplUninstallRemove.java b/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
similarity index 98%
rename from tests/src/com/android/launcher3/dragging/TaplUninstallRemove.java
rename to tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
index 568fc9f..0b9de0f 100644
--- a/tests/src/com/android/launcher3/dragging/TaplUninstallRemove.java
+++ b/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
@@ -51,7 +51,7 @@
  * Test runs in Out of process (Oop) and In process (Ipc)
  * Test the behaviour of uninstalling and removing apps both from AllApps, Workspace and Hotseat.
  */
-public class TaplUninstallRemove extends AbstractLauncherUiTest {
+public class TaplUninstallRemoveTest extends AbstractLauncherUiTest {
 
     @Before
     public void setUp() throws Exception {
diff --git a/tests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt b/tests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
new file mode 100644
index 0000000..4ec5b0e
--- /dev/null
+++ b/tests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.folder
+
+import android.R
+import android.content.Context
+import android.os.Process
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.launcher3.LauncherPrefs
+import com.android.launcher3.LauncherPrefs.Companion.get
+import com.android.launcher3.icons.BaseIconFactory
+import com.android.launcher3.icons.FastBitmapDrawable
+import com.android.launcher3.icons.UserBadgeDrawable
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.util.ActivityContextWrapper
+import com.android.launcher3.util.FlagOp
+import com.android.launcher3.util.LauncherLayoutBuilder
+import com.android.launcher3.util.LauncherModelHelper
+import com.android.launcher3.util.UserIconInfo
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** Tests for [PreviewItemManager] */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class PreviewItemManagerTest {
+
+    private lateinit var previewItemManager: PreviewItemManager
+    private lateinit var context: Context
+    private lateinit var folderItems: ArrayList<WorkspaceItemInfo>
+    private lateinit var modelHelper: LauncherModelHelper
+    private lateinit var folderIcon: FolderIcon
+
+    @Before
+    fun setup() {
+        getInstrumentation().runOnMainSync {
+            folderIcon =
+                FolderIcon(ActivityContextWrapper(ApplicationProvider.getApplicationContext()))
+        }
+        context = getInstrumentation().targetContext
+        previewItemManager = PreviewItemManager(folderIcon)
+        modelHelper = LauncherModelHelper()
+        modelHelper
+            .setupDefaultLayoutProvider(
+                LauncherLayoutBuilder()
+                    .atWorkspace(0, 0, 1)
+                    .putFolder(R.string.copy)
+                    .addApp(LauncherModelHelper.TEST_PACKAGE, LauncherModelHelper.TEST_ACTIVITY)
+                    .addApp(LauncherModelHelper.TEST_PACKAGE, LauncherModelHelper.TEST_ACTIVITY2)
+                    .addApp(LauncherModelHelper.TEST_PACKAGE, LauncherModelHelper.TEST_ACTIVITY3)
+                    .addApp(LauncherModelHelper.TEST_PACKAGE, LauncherModelHelper.TEST_ACTIVITY4)
+                    .build()
+            )
+            .loadModelSync()
+        folderItems = modelHelper.bgDataModel.folders.valueAt(0).contents
+        folderIcon.mInfo = modelHelper.bgDataModel.folders.valueAt(0)
+        folderIcon.mInfo.contents = folderItems
+
+        // Set first icon to be themed.
+        folderItems[0]
+            .bitmap
+            .setMonoIcon(
+                folderItems[0].bitmap.icon,
+                BaseIconFactory(
+                    context,
+                    context.resources.configuration.densityDpi,
+                    previewItemManager.mIconSize
+                )
+            )
+
+        // Set second icon to be non-themed.
+        folderItems[1]
+            .bitmap
+            .setMonoIcon(
+                null,
+                BaseIconFactory(
+                    context,
+                    context.resources.configuration.densityDpi,
+                    previewItemManager.mIconSize
+                )
+            )
+
+        // Set third icon to be themed with badge.
+        folderItems[2]
+            .bitmap
+            .setMonoIcon(
+                folderItems[2].bitmap.icon,
+                BaseIconFactory(
+                    context,
+                    context.resources.configuration.densityDpi,
+                    previewItemManager.mIconSize
+                )
+            )
+        folderItems[2].bitmap =
+            folderItems[2].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK))
+
+        // Set fourth icon to be non-themed with badge.
+        folderItems[3].bitmap =
+            folderItems[3].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK))
+        folderItems[3]
+            .bitmap
+            .setMonoIcon(
+                null,
+                BaseIconFactory(
+                    context,
+                    context.resources.configuration.densityDpi,
+                    previewItemManager.mIconSize
+                )
+            )
+    }
+    @After
+    @Throws(Exception::class)
+    fun tearDown() {
+        modelHelper.destroy()
+    }
+
+    @Test
+    fun checkThemedIconWithThemingOn_iconShouldBeThemed() {
+        get(context).put(LauncherPrefs.THEMED_ICONS, true)
+        val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
+
+        previewItemManager.setDrawable(drawingParams, folderItems[0])
+
+        assert((drawingParams.drawable as FastBitmapDrawable).isThemed)
+    }
+
+    @Test
+    fun checkThemedIconWithThemingOff_iconShouldNotBeThemed() {
+        get(context).put(LauncherPrefs.THEMED_ICONS, false)
+        val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
+
+        previewItemManager.setDrawable(drawingParams, folderItems[0])
+
+        assert(!(drawingParams.drawable as FastBitmapDrawable).isThemed)
+    }
+
+    @Test
+    fun checkUnthemedIconWithThemingOn_iconShouldNotBeThemed() {
+        get(context).put(LauncherPrefs.THEMED_ICONS, true)
+        val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
+
+        previewItemManager.setDrawable(drawingParams, folderItems[1])
+
+        assert(!(drawingParams.drawable as FastBitmapDrawable).isThemed)
+    }
+
+    @Test
+    fun checkUnthemedIconWithThemingOff_iconShouldNotBeThemed() {
+        get(context).put(LauncherPrefs.THEMED_ICONS, false)
+        val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
+
+        previewItemManager.setDrawable(drawingParams, folderItems[1])
+
+        assert(!(drawingParams.drawable as FastBitmapDrawable).isThemed)
+    }
+
+    @Test
+    fun checkThemedIconWithBadgeWithThemingOn_iconAndBadgeShouldBeThemed() {
+        get(context).put(LauncherPrefs.THEMED_ICONS, true)
+        val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
+
+        previewItemManager.setDrawable(drawingParams, folderItems[2])
+
+        assert((drawingParams.drawable as FastBitmapDrawable).isThemed)
+        assert(
+            ((drawingParams.drawable as FastBitmapDrawable).badge as UserBadgeDrawable).mIsThemed
+        )
+    }
+
+    @Test
+    fun checkUnthemedIconWithBadgeWithThemingOn_badgeShouldBeThemed() {
+        get(context).put(LauncherPrefs.THEMED_ICONS, true)
+        val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
+
+        previewItemManager.setDrawable(drawingParams, folderItems[3])
+
+        assert(!(drawingParams.drawable as FastBitmapDrawable).isThemed)
+        assert(
+            ((drawingParams.drawable as FastBitmapDrawable).badge as UserBadgeDrawable).mIsThemed
+        )
+    }
+
+    @Test
+    fun checkUnthemedIconWithBadgeWithThemingOff_iconAndBadgeShouldNotBeThemed() {
+        get(context).put(LauncherPrefs.THEMED_ICONS, false)
+        val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
+
+        previewItemManager.setDrawable(drawingParams, folderItems[3])
+
+        assert(!(drawingParams.drawable as FastBitmapDrawable).isThemed)
+        assert(
+            !((drawingParams.drawable as FastBitmapDrawable).badge as UserBadgeDrawable).mIsThemed
+        )
+    }
+
+    private fun profileFlagOp(type: Int) =
+        UserIconInfo(Process.myUserHandle(), type).applyBitmapInfoFlags(FlagOp.NO_OP)
+}
diff --git a/tests/src/com/android/launcher3/nonquickstep/HotseatWidthCalculationTest.kt b/tests/src/com/android/launcher3/nonquickstep/HotseatWidthCalculationTest.kt
index d102397..9912a34 100644
--- a/tests/src/com/android/launcher3/nonquickstep/HotseatWidthCalculationTest.kt
+++ b/tests/src/com/android/launcher3/nonquickstep/HotseatWidthCalculationTest.kt
@@ -47,7 +47,7 @@
         assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(177)
 
         assertThat(dp.isQsbInline).isFalse()
-        assertThat(dp.hotseatQsbWidth).isEqualTo(1445)
+        assertThat(dp.hotseatQsbWidth).isEqualTo(1435)
     }
 
     /**
@@ -69,7 +69,7 @@
         assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(110)
 
         assertThat(dp.isQsbInline).isFalse()
-        assertThat(dp.hotseatQsbWidth).isEqualTo(1080)
+        assertThat(dp.hotseatQsbWidth).isEqualTo(1070)
     }
 
     /**
@@ -90,7 +90,7 @@
         assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(370)
 
         assertThat(dp.isQsbInline).isFalse()
-        assertThat(dp.hotseatQsbWidth).isEqualTo(1468)
+        assertThat(dp.hotseatQsbWidth).isEqualTo(1455)
     }
 
     /**
@@ -115,7 +115,7 @@
         assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(668)
 
         assertThat(dp.isQsbInline).isFalse()
-        assertThat(dp.hotseatQsbWidth).isEqualTo(1224)
+        assertThat(dp.hotseatQsbWidth).isEqualTo(1214)
     }
 
     /** This is a case when after setting the hotseat, the QSB width needs to be changed to fit */
@@ -134,7 +134,7 @@
         assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(640)
 
         assertThat(dp.isQsbInline).isFalse()
-        assertThat(dp.hotseatQsbWidth).isEqualTo(1179)
+        assertThat(dp.hotseatQsbWidth).isEqualTo(1169)
     }
 
     /**
@@ -156,7 +156,7 @@
         assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(582)
 
         assertThat(dp.isQsbInline).isFalse()
-        assertThat(dp.hotseatQsbWidth).isEqualTo(1095)
+        assertThat(dp.hotseatQsbWidth).isEqualTo(1085)
     }
 
     @Test
@@ -176,7 +176,7 @@
 
             assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(177)
             assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(177)
-            assertThat(dp.hotseatQsbWidth).isEqualTo(1445)
+            assertThat(dp.hotseatQsbWidth).isEqualTo(1435)
         }
     }
 }
diff --git a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
index 10d9133..733f1e9 100644
--- a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
+++ b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
@@ -59,6 +59,7 @@
 import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.backuprestore.LauncherRestoreEventLogger;
 import com.android.launcher3.model.ModelDbController;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.LauncherModelHelper;
@@ -90,6 +91,7 @@
     private SQLiteDatabase mMockDb;
     private Cursor mMockCursor;
     private LauncherPrefs mPrefs;
+    private LauncherRestoreEventLogger mMockRestoreEventLogger;
 
     @Before
     public void setup() {
@@ -100,6 +102,7 @@
         mMockDb = mock(SQLiteDatabase.class);
         mMockCursor = mock(Cursor.class);
         mPrefs = new LauncherPrefs(mContext);
+        mMockRestoreEventLogger = mock(LauncherRestoreEventLogger.class);
     }
 
     @After
@@ -178,7 +181,7 @@
         assertEquals(10, getItemCountForProfile(db, myProfileId_old));
         assertEquals(6, getItemCountForProfile(db, workProfileId_old));
 
-        mTask.sanitizeDB(mContext, controller, controller.getDb(), bm);
+        mTask.sanitizeDB(mContext, controller, controller.getDb(), bm, mMockRestoreEventLogger);
 
         // All the data has been migrated to the new user ids
         assertEquals(0, getItemCountForProfile(db, myProfileId_old));
@@ -207,7 +210,7 @@
         assertEquals(10, getItemCountForProfile(db, myProfileId_old));
         assertEquals(6, getItemCountForProfile(db, workProfileId_old));
 
-        mTask.sanitizeDB(mContext, controller, controller.getDb(), bm);
+        mTask.sanitizeDB(mContext, controller, controller.getDb(), bm, mMockRestoreEventLogger);
 
         // All the data has been migrated to the new user ids
         assertEquals(0, getItemCountForProfile(db, myProfileId_old));
@@ -219,7 +222,7 @@
     @Test
     public void givenLauncherPrefsHasNoIds_whenRestoreAppWidgetIdsIfExists_thenIdsAreRemoved() {
         // When
-        mTask.restoreAppWidgetIdsIfExists(mContext, mMockController);
+        mTask.restoreAppWidgetIdsIfExists(mContext, mMockController, mMockRestoreEventLogger);
         // Then
         assertThat(mPrefs.has(OLD_APP_WIDGET_IDS, APP_WIDGET_IDS)).isFalse();
     }
@@ -235,7 +238,7 @@
 
         // When
         setRestoredAppWidgetIds(mContext, expectedOldIds, expectedNewIds);
-        mTask.restoreAppWidgetIdsIfExists(mContext, mMockController);
+        mTask.restoreAppWidgetIdsIfExists(mContext, mMockController, mMockRestoreEventLogger);
 
         // Then
         assertThat(expectedHost.getAppWidgetIds()).isEqualTo(expectedOldIds);
@@ -257,7 +260,7 @@
 
         // When
         setRestoredAppWidgetIds(mContext, expectedOldIds, expectedNewIds);
-        mTask.restoreAppWidgetIdsIfExists(mContext, mMockController);
+        mTask.restoreAppWidgetIdsIfExists(mContext, mMockController, mMockRestoreEventLogger);
 
         // Then
         assertThat(expectedHost.getAppWidgetIds()).isEqualTo(expectedOldIds);
@@ -280,12 +283,13 @@
         when(mMockDb.query(any(), any(), any(), any(), any(), any(), any()))
                 .thenReturn(mMockCursor);
         when(mMockCursor.moveToFirst()).thenReturn(true);
+        when(mMockCursor.getColumnNames()).thenReturn(new String[] {});
         when(mMockCursor.isAfterLast()).thenReturn(true);
         RestoreDbTask.setPending(mContext);
 
         // When
         setRestoredAppWidgetIds(mContext, expectedOldIds, expectedNewIds);
-        mTask.restoreAppWidgetIdsIfExists(mContext, mMockController);
+        mTask.restoreAppWidgetIdsIfExists(mContext, mMockController, mMockRestoreEventLogger);
 
         // Then
         assertThat(expectedHost.getAppWidgetIds()).isEqualTo(allExpectedIds);
diff --git a/tests/src/com/android/launcher3/responsive/ResponsiveSpecsProviderTest.kt b/tests/src/com/android/launcher3/responsive/ResponsiveSpecsProviderTest.kt
index 9681ca8..54a1dc5 100644
--- a/tests/src/com/android/launcher3/responsive/ResponsiveSpecsProviderTest.kt
+++ b/tests/src/com/android/launcher3/responsive/ResponsiveSpecsProviderTest.kt
@@ -84,7 +84,7 @@
                     startPadding = SizeSpec(1f.dpToPx()),
                     endPadding = SizeSpec(1f.dpToPx()),
                     gutter = SizeSpec(8f.dpToPx()),
-                    cellSize = SizeSpec(ofRemainderSpace = .25f)
+                    cellSize = SizeSpec(ofRemainderSpace = 1f)
                 )
             )
 
@@ -97,7 +97,7 @@
                     startPadding = SizeSpec(2f.dpToPx()),
                     endPadding = SizeSpec(2f.dpToPx()),
                     gutter = SizeSpec(8f.dpToPx()),
-                    cellSize = SizeSpec(ofRemainderSpace = .25f)
+                    cellSize = SizeSpec(ofRemainderSpace = 1f)
                 ),
             )
 
@@ -182,7 +182,7 @@
                     startPadding = SizeSpec(22f.dpToPx()),
                     endPadding = SizeSpec(22f.dpToPx()),
                     gutter = sizeSpec16,
-                    cellSize = SizeSpec(ofRemainderSpace = .25f)
+                    cellSize = SizeSpec(ofRemainderSpace = 1f)
                 )
             )
 
@@ -231,7 +231,7 @@
                     startPadding = SizeSpec(0f.dpToPx()),
                     endPadding = SizeSpec(36f.dpToPx()),
                     gutter = sizeSpec12,
-                    cellSize = SizeSpec(ofRemainderSpace = .25f)
+                    cellSize = SizeSpec(ofRemainderSpace = 1f)
                 ),
                 ResponsiveSpec(
                     maxAvailableSize = 716.dpToPx(),
@@ -240,7 +240,7 @@
                     startPadding = SizeSpec(16f.dpToPx()),
                     endPadding = SizeSpec(64f.dpToPx()),
                     gutter = sizeSpec12,
-                    cellSize = SizeSpec(ofRemainderSpace = .25f)
+                    cellSize = SizeSpec(ofRemainderSpace = 1f)
                 ),
                 ResponsiveSpec(
                     maxAvailableSize = 9999.dpToPx(),
@@ -249,7 +249,7 @@
                     startPadding = SizeSpec(36f.dpToPx()),
                     endPadding = SizeSpec(80f.dpToPx()),
                     gutter = sizeSpec12,
-                    cellSize = SizeSpec(ofRemainderSpace = .25f)
+                    cellSize = SizeSpec(ofRemainderSpace = 1f)
                 )
             )
 
@@ -262,7 +262,7 @@
                     startPadding = SizeSpec(0f),
                     endPadding = SizeSpec(24f.dpToPx()),
                     gutter = sizeSpec12,
-                    cellSize = SizeSpec(ofRemainderSpace = .25f)
+                    cellSize = SizeSpec(ofRemainderSpace = 1f)
                 ),
                 ResponsiveSpec(
                     maxAvailableSize = 9999.dpToPx(),
@@ -271,7 +271,7 @@
                     startPadding = SizeSpec(0f),
                     endPadding = SizeSpec(34f.dpToPx()),
                     gutter = sizeSpec12,
-                    cellSize = SizeSpec(ofRemainderSpace = .25f)
+                    cellSize = SizeSpec(ofRemainderSpace = 1f)
                 ),
             )
 
diff --git a/tests/src/com/android/launcher3/responsive/WorkspaceSpecsTest.kt b/tests/src/com/android/launcher3/responsive/WorkspaceSpecsTest.kt
index 9781645..17b0ee4 100644
--- a/tests/src/com/android/launcher3/responsive/WorkspaceSpecsTest.kt
+++ b/tests/src/com/android/launcher3/responsive/WorkspaceSpecsTest.kt
@@ -159,7 +159,7 @@
                     "maxSize=2147483647), " +
                     "cellSize=SizeSpec(fixedSize=0.0, " +
                     "ofAvailableSpace=0.0, " +
-                    "ofRemainderSpace=0.25, " +
+                    "ofRemainderSpace=1.0, " +
                     "matchWorkspace=false, " +
                     "maxSize=2147483647)" +
                     ")"
diff --git a/tests/src/com/android/launcher3/tapl/TaplUtilityTest.java b/tests/src/com/android/launcher3/tapl/TaplUtilityTest.java
new file mode 100644
index 0000000..b6ded97
--- /dev/null
+++ b/tests/src/com/android/launcher3/tapl/TaplUtilityTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.tapl;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class TaplUtilityTest {
+
+    @Test
+    public void testMakeMultilinePattern() {
+        // Original title will match.
+        assertTrue(AppIcon.makeMultilinePattern("Play Store")
+                .matcher("Play Store").matches());
+        assertTrue(AppIcon.makeMultilinePattern("PlayStore")
+                .matcher("PlayStore").matches());
+
+        // Original title with whitespace added will match.
+        assertTrue(AppIcon.makeMultilinePattern("PlayStore")
+                .matcher("Play\nStore").matches());
+        assertTrue(AppIcon.makeMultilinePattern("PlayStore")
+                .matcher("Play Store").matches());
+        // Original title with whitespace removed will also match.
+        assertTrue(AppIcon.makeMultilinePattern("Play Store")
+                .matcher("PlayStore").matches());
+        // Or whitespace replaced with a different kind of whitespace (both of above conditions).
+        assertTrue(AppIcon.makeMultilinePattern("Play Store")
+                .matcher("Play\nStore").matches());
+        assertTrue(AppIcon.makeMultilinePattern("Play Store")
+                .matcher("Play \n Store").matches());
+
+        // Any non-whitespace character added to the title will not match.
+        assertFalse(AppIcon.makeMultilinePattern("Play Store")
+                .matcher("Play Store has 7 notifications").matches());
+        assertFalse(AppIcon.makeMultilinePattern("Play Store")
+                .matcher("Play  Store!").matches());
+        // Title is case-sensitive.
+        assertFalse(AppIcon.makeMultilinePattern("Play Store")
+                .matcher("play store").matches());
+        assertFalse(AppIcon.makeMultilinePattern("Play Store")
+                .matcher("play  store").matches());
+        // Removing non whitespace characters will not match.
+        assertFalse(AppIcon.makeMultilinePattern("Play Store")
+                .matcher("").matches());
+        assertFalse(AppIcon.makeMultilinePattern("Play Store")
+                .matcher("Play Stor").matches());
+        assertFalse(AppIcon.makeMultilinePattern("Play Store")
+                .matcher("Play").matches());
+    }
+}
diff --git a/tests/src/com/android/launcher3/tapl/TaplUtilityTests.java b/tests/src/com/android/launcher3/tapl/TaplUtilityTests.java
deleted file mode 100644
index 15db1d8..0000000
--- a/tests/src/com/android/launcher3/tapl/TaplUtilityTests.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.tapl;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import org.junit.Test;
-
-public class TaplUtilityTests {
-
-    @Test
-    public void testNewStringWithRegex() {
-        assertTrue(AppIcon.makeMultilinePattern("Play Store")
-                .matcher("Play Store has 7 notifications").matches());
-        assertTrue(AppIcon.makeMultilinePattern("Play Store")
-                .matcher("Play  Store!").matches());
-        assertFalse(AppIcon.makeMultilinePattern("Play Store")
-                .matcher("play  store").matches());
-        assertFalse(AppIcon.makeMultilinePattern("Play Store")
-                .matcher("").matches());
-        assertTrue(AppIcon.makeMultilinePattern("Play Store")
-                .matcher("Play \n Store").matches());
-    }
-}
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3Test.java
similarity index 95%
rename from tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
rename to tests/src/com/android/launcher3/ui/TaplTestsLauncher3Test.java
index 229ea45..d26a9db 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3Test.java
@@ -27,7 +27,7 @@
 
 @LargeTest
 @RunWith(AndroidJUnit4.class)
-public class TaplTestsLauncher3 extends AbstractLauncherUiTest {
+public class TaplTestsLauncher3Test extends AbstractLauncherUiTest {
 
     @Before
     public void setUp() throws Exception {
diff --git a/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java b/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java
index f818564..cb30854 100644
--- a/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java
+++ b/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java
@@ -19,7 +19,10 @@
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.allapps.AllAppsStore.DEFER_UPDATES_TEST;
+import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
 import static com.android.launcher3.util.TestUtil.installDummyAppForUser;
+import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
+import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -39,12 +42,13 @@
 import com.android.launcher3.allapps.WorkProfileManager;
 import com.android.launcher3.tapl.LauncherInstrumentation;
 import com.android.launcher3.util.TestUtil;
+import com.android.launcher3.util.rule.TestStabilityRule;
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 
+import java.io.IOException;
 import java.util.Objects;
 import java.util.function.Predicate;
 
@@ -98,7 +102,17 @@
             launcher.getAppsView().getAppsStore().disableDeferUpdates(DEFER_UPDATES_TEST);
         });
         TestUtil.uninstallDummyApp();
-        mDevice.executeShellCommand("pm remove-user " + mProfileUserId);
+
+        mLauncher.runToState(
+                () -> {
+                    try {
+                        mDevice.executeShellCommand("pm remove-user " + mProfileUserId);
+                    } catch (IOException e) {
+                        throw new RuntimeException(e);
+                    }
+                },
+                NORMAL_STATE_ORDINAL,
+                "executing pm 'remove-user' command");
     }
 
     private void waitForWorkTabSetup() {
@@ -123,8 +137,10 @@
                 LauncherInstrumentation.WAIT_TIME_MS);
     }
 
+    // Staging; will be promoted to presubmit if stable
+    @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT)
+
     @Test
-    @Ignore("b/243855320")
     public void toggleWorks() {
         assumeTrue(mWorkProfileSetupSuccessful);
         waitForWorkTabSetup();
diff --git a/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java
index 27fda9b..db38c68 100644
--- a/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java
@@ -18,7 +18,6 @@
 import static org.junit.Assert.assertNotNull;
 
 import android.platform.test.annotations.PlatinumTest;
-import android.platform.test.rule.ScreenRecordRule;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.LargeTest;
@@ -29,9 +28,11 @@
 import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
 import com.android.launcher3.ui.TestViewHelpers;
+import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
 import com.android.launcher3.util.rule.ShellCommandRule;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 
+import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
@@ -47,10 +48,16 @@
     @Rule
     public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
 
-    @PlatinumTest(focusArea = "launcher")
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        AbstractLauncherUiTest.initialize(this);
+    }
+
     @Test
     @PortraitLandscape
-    @ScreenRecordRule.ScreenRecord // b/289161193
+    @ScreenRecord // b/316910614
     public void testDragIcon() throws Throwable {
         mLauncher.enableDebugTracing(); // b/289161193
         new FavoriteItemsTransaction(mTargetContext).commitAndLoadHome(mLauncher);
@@ -100,6 +107,7 @@
      */
     @PlatinumTest(focusArea = "launcher")
     @Test
+    @ScreenRecord // b/316910614
     public void testResizeWidget() throws Throwable {
         new FavoriteItemsTransaction(mTargetContext).commitAndLoadHome(mLauncher);
 
diff --git a/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java b/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java
index 59c82a7..4edeb42 100644
--- a/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java
@@ -15,7 +15,6 @@
  */
 package com.android.launcher3.ui.workspace;
 
-import static com.android.launcher3.ui.AbstractLauncherUiTest.initialize;
 import static com.android.launcher3.util.TestConstants.AppNames.CHROME_APP_NAME;
 
 import static org.junit.Assert.assertEquals;
@@ -23,8 +22,6 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
-import android.platform.test.annotations.PlatinumTest;
-
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.tapl.HomeAppIcon;
@@ -70,7 +67,6 @@
      * move between workspaces. After, make sure we can launch an app from the Workspace.
      * @throws Exception if we can't set the defaults icons that will appear at the beginning.
      */
-    @PlatinumTest(focusArea = "launcher")
     @Test
     public void testWorkspace() throws Exception {
         // Set workspace  that includes the chrome Activity app icon on the hotseat.
@@ -123,7 +119,6 @@
      * Similar to {@link TaplWorkspaceTest#testWorkspace} but here we also make sure we can delete
      * the pages.
      */
-    @PlatinumTest(focusArea = "launcher")
     @Test
     public void testAddAndDeletePageAndFling() {
         Workspace workspace = mLauncher.getWorkspace();
diff --git a/tests/src/com/android/launcher3/util/ExecutorRunnableTest.kt b/tests/src/com/android/launcher3/util/ExecutorRunnableTest.kt
index b8d74aa..972b592 100644
--- a/tests/src/com/android/launcher3/util/ExecutorRunnableTest.kt
+++ b/tests/src/com/android/launcher3/util/ExecutorRunnableTest.kt
@@ -16,7 +16,7 @@
 
 package com.android.launcher3.util
 
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.launcher3.util.rule.TestStabilityRule
 import java.util.concurrent.ExecutorService
@@ -24,12 +24,13 @@
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 
 /** Unit test for [ExecutorRunnable] */
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class ExecutorRunnableTest {
 
     private lateinit var underTest: ExecutorRunnable<Int>
@@ -38,6 +39,8 @@
     private var isTaskExecuted = false
     private var isCallbackExecuted = false
 
+    @get:Rule(order = 0) val testStabilityRule = TestStabilityRule()
+
     @Before
     fun setup() {
         reset()
@@ -60,8 +63,8 @@
     fun run_and_complete() {
         awaitAllExecutorCompleted()
 
-        assertTrue(isTaskExecuted)
-        assertTrue(isCallbackExecuted)
+        assertTrue("task should be executed", isTaskExecuted)
+        assertTrue("callback should be executed", isCallbackExecuted)
         assertEquals(2, result)
     }
 
@@ -73,7 +76,7 @@
         underTest.cancel(false)
         awaitAllExecutorCompleted()
 
-        assertFalse(isCallbackExecuted)
+        assertFalse("callback should not be executed.", isCallbackExecuted)
         assertEquals(0, result)
     }
 
@@ -83,8 +86,8 @@
 
         underTest.cancel(false)
 
-        assertTrue(isTaskExecuted)
-        assertTrue(isCallbackExecuted)
+        assertTrue("task should be executed", isTaskExecuted)
+        assertTrue("callback should be executed", isCallbackExecuted)
         assertEquals(2, result)
     }
 
diff --git a/tests/src/com/android/launcher3/util/ModelTestExtensions.kt b/tests/src/com/android/launcher3/util/ModelTestExtensions.kt
index 61ec669..6bd182b 100644
--- a/tests/src/com/android/launcher3/util/ModelTestExtensions.kt
+++ b/tests/src/com/android/launcher3/util/ModelTestExtensions.kt
@@ -1,7 +1,27 @@
 package com.android.launcher3.util
 
+import android.content.ContentValues
 import com.android.launcher3.LauncherModel
+import com.android.launcher3.LauncherSettings.Favorites
+import com.android.launcher3.LauncherSettings.Favorites.APPWIDGET_ID
+import com.android.launcher3.LauncherSettings.Favorites.APPWIDGET_PROVIDER
+import com.android.launcher3.LauncherSettings.Favorites.APPWIDGET_SOURCE
+import com.android.launcher3.LauncherSettings.Favorites.CELLX
+import com.android.launcher3.LauncherSettings.Favorites.CELLY
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP
+import com.android.launcher3.LauncherSettings.Favorites.INTENT
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
+import com.android.launcher3.LauncherSettings.Favorites.PROFILE_ID
+import com.android.launcher3.LauncherSettings.Favorites.RESTORED
+import com.android.launcher3.LauncherSettings.Favorites.SCREEN
+import com.android.launcher3.LauncherSettings.Favorites.SPANX
+import com.android.launcher3.LauncherSettings.Favorites.SPANY
+import com.android.launcher3.LauncherSettings.Favorites.TITLE
+import com.android.launcher3.LauncherSettings.Favorites._ID
 import com.android.launcher3.model.BgDataModel
+import com.android.launcher3.model.ModelDbController
 
 object ModelTestExtensions {
     /** Clears and reloads Launcher db to cleanup the workspace */
@@ -10,7 +30,7 @@
         loadModelSync()
         TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) {
             modelDbController.run {
-                tryMigrateDB()
+                tryMigrateDB(null /* restoreEventLogger */)
                 createEmptyDB()
                 clearEmptyDbFlag()
             }
@@ -20,6 +40,7 @@
         loadModelSync()
     }
 
+    /** Loads the model in memory synchronously */
     fun LauncherModel.loadModelSync() {
         val mockCb: BgDataModel.Callbacks = object : BgDataModel.Callbacks {}
         TestUtil.runOnExecutorSync(Executors.MAIN_EXECUTOR) { addCallbacksAndLoad(mockCb) }
@@ -27,4 +48,54 @@
         TestUtil.runOnExecutorSync(Executors.MAIN_EXECUTOR) {}
         TestUtil.runOnExecutorSync(Executors.MAIN_EXECUTOR) { removeCallbacks(mockCb) }
     }
+
+    /** Adds and commits a new item to Launcher.db */
+    fun LauncherModel.addItem(
+        title: String = "LauncherTestApp",
+        intent: String =
+            "#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;component=com.google.android.apps.nexuslauncher.tests/com.android.launcher3.testcomponent.BaseTestingActivity;launchFlags=0x10200000;end",
+        type: Int = ITEM_TYPE_APPLICATION,
+        restoreFlags: Int = 0,
+        screen: Int = 0,
+        container: Int = CONTAINER_DESKTOP,
+        x: Int,
+        y: Int,
+        spanX: Int = 1,
+        spanY: Int = 1,
+        id: Int = 0,
+        profileId: Int = 0,
+        tableName: String = Favorites.TABLE_NAME,
+        appWidgetId: Int = -1,
+        appWidgetSource: Int = -1,
+        appWidgetProvider: String? = null
+    ) {
+        loadModelSync()
+        TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) {
+            val controller: ModelDbController = modelDbController
+            controller.tryMigrateDB(null /* restoreEventLogger */)
+            modelDbController.newTransaction().use { transaction ->
+                val values =
+                    ContentValues().apply {
+                        values[_ID] = id
+                        values[TITLE] = title
+                        values[PROFILE_ID] = profileId
+                        values[CONTAINER] = container
+                        values[SCREEN] = screen
+                        values[CELLX] = x
+                        values[CELLY] = y
+                        values[SPANX] = spanX
+                        values[SPANY] = spanY
+                        values[ITEM_TYPE] = type
+                        values[RESTORED] = restoreFlags
+                        values[INTENT] = intent
+                        values[APPWIDGET_ID] = appWidgetId
+                        values[APPWIDGET_SOURCE] = appWidgetSource
+                        values[APPWIDGET_PROVIDER] = appWidgetProvider
+                    }
+                // Migrate any previous data so that the DB state is correct
+                controller.insert(tableName, values)
+                transaction.commit()
+            }
+        }
+    }
 }
diff --git a/tests/src/com/android/launcher3/util/TestUtil.java b/tests/src/com/android/launcher3/util/TestUtil.java
index 683f323..95444ba 100644
--- a/tests/src/com/android/launcher3/util/TestUtil.java
+++ b/tests/src/com/android/launcher3/util/TestUtil.java
@@ -103,7 +103,9 @@
             out.close();
 
             final String result = UiDevice.getInstance(instrumentation)
-                    .executeShellCommand("pm install --user " + userId + " " + apkFilename);
+                    .executeShellCommand(String.format("pm install -i %s --user ",
+                            instrumentation.getContext().getPackageName())
+                            + userId + " " + apkFilename);
             Assert.assertTrue(
                     "Failed to install wellbeing test apk; make sure the device is rooted",
                     "Success".equals(result.replaceAll("\\s+", "")));
diff --git a/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java b/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java
index b51045f..909aabd 100644
--- a/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java
+++ b/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java
@@ -83,6 +83,7 @@
 
     public static int getRunFlavor() {
         if (sRunFlavor != 0) return sRunFlavor;
+        if (isRobolectricTest()) return PLATFORM_POSTSUBMIT;
 
         final String flavorOverride = InstrumentationRegistry.getArguments().getString("flavor");
 
@@ -150,4 +151,8 @@
     public static boolean isPresubmit() {
         return getRunFlavor() == PLATFORM_PRESUBMIT;
     }
+
+    public static boolean isRobolectricTest() {
+        return Build.FINGERPRINT.contains("robolectric");
+    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index 0e78565..2dbc29d 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -21,6 +21,7 @@
 
 import static com.android.launcher3.tapl.LauncherInstrumentation.DEFAULT_POLL_INTERVAL;
 import static com.android.launcher3.tapl.LauncherInstrumentation.WAIT_TIME_MS;
+import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
 
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -362,6 +363,7 @@
 
     /**
      * Taps outside bottom sheet to dismiss it. Available on tablets only.
+     *
      * @param tapRight Tap on the right of bottom sheet if true, or left otherwise.
      */
     public void dismissByTappingOutsideForTablet(boolean tapRight) {
@@ -375,7 +377,7 @@
                     ? mLauncher.waitForLauncherObject(FAST_SCROLLER_RES_ID) :
                     mLauncher.waitForLauncherObject(BOTTOM_SHEET_RES_ID);
 
-            mLauncher.touchOutsideContainer(container, tapRight, false);
+            touchOutside(tapRight, container);
             try (LauncherInstrumentation.Closable tapped = mLauncher.addContextLayer(
                     "tapped outside AllApps bottom sheet")) {
                 verifyVisibleContainerOnDismiss();
@@ -383,10 +385,14 @@
         }
     }
 
+    protected void touchOutside(boolean tapRight, UiObject2 container) {
+        mLauncher.touchOutsideContainer(container, tapRight, false);
+    }
+
     /** Presses the meta keyboard shortcut to dismiss AllApps. */
     public void dismissByKeyboardShortcut() {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
-            mLauncher.getDevice().pressKeyCode(KEYCODE_META_RIGHT);
+            pressMetaKey();
             try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                     "pressed meta key")) {
                 verifyVisibleContainerOnDismiss();
@@ -394,12 +400,19 @@
         }
     }
 
+    protected void pressMetaKey() {
+        mLauncher.getDevice().pressKeyCode(KEYCODE_META_RIGHT);
+    }
+
     /** Presses the esc key to dismiss AllApps. */
     public void dismissByEscKey() {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
             mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_ESC_DOWN);
             mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_ESC_UP);
-            mLauncher.getDevice().pressKeyCode(KEYCODE_ESCAPE);
+            mLauncher.runToState(
+                    () -> mLauncher.getDevice().pressKeyCode(KEYCODE_ESCAPE),
+                    NORMAL_STATE_ORDINAL,
+                    "pressing esc key");
             try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                     "pressed esc key")) {
                 verifyVisibleContainerOnDismiss();
@@ -411,6 +424,7 @@
 
     /**
      * Return the QSB UI object on the AllApps screen.
+     *
      * @return the QSB UI object.
      */
     @NonNull
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIcon.java b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
index 85098c8..867a1a8 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
@@ -36,9 +36,23 @@
         super(launcher, icon);
     }
 
+    /**
+     * Find an app icon with the given name.
+     *
+     * @param appName app icon to look for
+     */
+    static BySelector getAppIconSelector(String appName) {
+        return By.clazz(TextView.class).text(makeMultilinePattern(appName));
+    }
+
+    /**
+     * Find an app icon with the given name.
+     *
+     * @param appName app icon to look for
+     * @param launcher (optional) - only match ui elements from Launcher's package
+     */
     static BySelector getAppIconSelector(String appName, LauncherInstrumentation launcher) {
-        return By.clazz(TextView.class).desc(makeMultilinePattern(appName))
-                .pkg(launcher.getLauncherPackageName());
+        return getAppIconSelector(appName).pkg(launcher.getLauncherPackageName());
     }
 
     static BySelector getMenuItemSelector(String text, LauncherInstrumentation launcher) {
@@ -109,13 +123,16 @@
     }
 
     /**
-     * Create a regular expression pattern that matches strings starting with the app name, where
-     * spaces in the app name are replaced with zero or more occurrences of the "\s" character
-     * (which represents a whitespace character in regular expressions), followed by any characters
-     * after the app name.
+     * Create a regular expression pattern that matches strings containing all of the non-whitespace
+     * characters of the app name, with any amount of whitespace added between characters (e.g.
+     * newline for multiline app labels).
      */
     static Pattern makeMultilinePattern(String appName) {
-        return Pattern.compile(appName.replaceAll("\\s+", "\\\\s*") + ".*",
-                Pattern.DOTALL);
+        // Remove any existing whitespace.
+        appName = appName.replaceAll("\\s", "");
+        // Allow whitespace between characters, e.g. newline for 2 line app label.
+        StringBuilder regexBuldier = new StringBuilder("\\s*");
+        appName.chars().forEach(letter -> regexBuldier.append((char) letter).append("\\s*"));
+        return Pattern.compile(regexBuldier.toString());
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index b6b4a47..1bc489c 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -16,7 +16,10 @@
 
 package com.android.launcher3.tapl;
 
+import static android.view.KeyEvent.KEYCODE_ESCAPE;
+
 import static com.android.launcher3.tapl.LauncherInstrumentation.TASKBAR_RES_ID;
+import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
 
 import android.graphics.Rect;
 
@@ -27,15 +30,24 @@
 import androidx.test.uiautomator.Direction;
 import androidx.test.uiautomator.UiObject2;
 
+import com.android.launcher3.testing.shared.TestProtocol;
+
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
+import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
 /**
  * Common overview panel for both Launcher and fallback recents
  */
 public class BaseOverview extends LauncherInstrumentation.VisibleContainer {
+
+    private static final Pattern EVENT_ALT_ESC_DOWN = Pattern.compile(
+            "Key event: KeyEvent.*?action=ACTION_DOWN.*?keyCode=KEYCODE_ESCAPE.*?metaState=0");
+    private static final Pattern EVENT_ALT_ESC_UP = Pattern.compile(
+            "Key event: KeyEvent.*?action=ACTION_UP.*?keyCode=KEYCODE_ESCAPE.*?metaState=0");
+
     private static final int FLINGS_FOR_DISMISS_LIMIT = 40;
 
     BaseOverview(LauncherInstrumentation launcher) {
@@ -128,8 +140,19 @@
                 flingForwardImpl();
             }
 
-            mLauncher.clickLauncherObject(
-                    mLauncher.waitForObjectInContainer(verifyActiveContainer(), clearAllSelector));
+            final Runnable clickClearAll = () -> mLauncher.clickLauncherObject(
+                    mLauncher.waitForObjectInContainer(verifyActiveContainer(),
+                            clearAllSelector));
+            if (mLauncher.is3PLauncher()) {
+                mLauncher.executeAndWaitForLauncherStop(
+                        clickClearAll,
+                        "clicking 'Clear All'");
+            } else {
+                mLauncher.runToState(
+                        clickClearAll,
+                        NORMAL_STATE_ORDINAL,
+                        "clicking 'Clear All'");
+            }
 
             mLauncher.waitUntilLauncherObjectGone(clearAllSelector);
         }
@@ -150,9 +173,12 @@
 
             OverviewTask currentTask = flingToFirstTask();
 
-            mLauncher.touchOutsideContainer(currentTask.getUiObject(),
-                    /* tapRight= */ true,
-                    /* halfwayToEdge= */ false);
+            mLauncher.runToState(
+                    () -> mLauncher.touchOutsideContainer(currentTask.getUiObject(),
+                            /* tapRight= */ true,
+                            /* halfwayToEdge= */ false),
+                    NORMAL_STATE_ORDINAL,
+                    "touching outside of first task");
 
             return new Workspace(mLauncher);
         }
@@ -185,11 +211,15 @@
     public void touchTaskbarBottomCorner(boolean tapRight) {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
             Taskbar taskbar = new Taskbar(mLauncher);
-            taskbar.touchBottomCorner(tapRight);
             if (mLauncher.isTransientTaskbar()) {
+                mLauncher.runToState(
+                        () -> taskbar.touchBottomCorner(tapRight),
+                        NORMAL_STATE_ORDINAL,
+                        "touching taskbar");
                 // Tapping outside Transient Taskbar returns to Workspace, wait for that state.
                 new Workspace(mLauncher);
             } else {
+                taskbar.touchBottomCorner(tapRight);
                 // Should stay in Overview.
                 verifyActiveContainer();
                 verifyActionsViewVisibility();
@@ -367,6 +397,23 @@
         return !task.isTaskSplit();
     }
 
+    /**
+     * Presses the esc key to dismiss Overview.
+     */
+    public Workspace dismissByEscKey() {
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+            mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_ESC_DOWN);
+            mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_ESC_UP);
+            mLauncher.runToState(
+                    () -> mLauncher.getDevice().pressKeyCode(KEYCODE_ESCAPE),
+                    NORMAL_STATE_ORDINAL, "pressing esc key");
+            try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                    "pressed esc key")) {
+                return mLauncher.getWorkspace();
+            }
+        }
+    }
+
     private void verifyActionsViewVisibility() {
         try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                 "want to assert overview actions view visibility")) {
diff --git a/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java b/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java
index 9ca2dc8..f8e1c10 100644
--- a/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java
@@ -30,8 +30,8 @@
 
     /**
      * Swipes up or down to dismiss to Workspace.
-     * @param swipeDown Swipe all apps down to dismiss, otherwise swipe up to dismiss by going home.
      *
+     * @param swipeDown Swipe all apps down to dismiss, otherwise swipe up to dismiss by going home.
      * @return the Workspace object.
      */
     @NonNull
@@ -131,4 +131,20 @@
             return new Workspace(mLauncher);
         }
     }
+
+    @Override
+    protected void touchOutside(boolean tapRight, UiObject2 container) {
+        mLauncher.runToState(
+                () -> super.touchOutside(tapRight, container),
+                NORMAL_STATE_ORDINAL,
+                "touching outside");
+    }
+
+    @Override
+    protected void pressMetaKey() {
+        mLauncher.runToState(
+                () -> super.pressMetaKey(),
+                NORMAL_STATE_ORDINAL,
+                "pressing meta key");
+    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/HomeQsb.java b/tests/tapl/com/android/launcher3/tapl/HomeQsb.java
index 5385c65..c1fc45f 100644
--- a/tests/tapl/com/android/launcher3/tapl/HomeQsb.java
+++ b/tests/tapl/com/android/launcher3/tapl/HomeQsb.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.tapl;
 
+import static com.android.launcher3.testing.shared.TestProtocol.ALL_APPS_STATE_ORDINAL;
+
 import androidx.test.uiautomator.UiObject2;
 
 /**
@@ -25,4 +27,13 @@
     HomeQsb(LauncherInstrumentation launcher, UiObject2 hotseat) {
         super(launcher, hotseat, "search_container_hotseat");
     }
+
+    @Override
+    protected void clickQsb() {
+        // Clicking Qsb will switch to All Apps state.
+        mLauncher.runToState(
+                () -> super.clickQsb(),
+                ALL_APPS_STATE_ORDINAL,
+                "Clicking Qsb");
+    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index fe927b3..28e2590 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -16,8 +16,6 @@
 
 package com.android.launcher3.tapl;
 
-import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
-
 import static com.android.launcher3.testing.shared.TestProtocol.SPRING_LOADED_STATE_ORDINAL;
 
 import android.graphics.Point;
@@ -97,12 +95,10 @@
             LauncherInstrumentation.log("Launchable.launch before click "
                     + mObject.getVisibleCenter() + " in " + mLauncher.getVisibleBounds(
                     mObject));
-            mLauncher.executeAndWaitForLauncherEvent(
+
+            mLauncher.executeAndWaitForLauncherStop(
                     () -> mLauncher.clickLauncherObject(mObject),
-                    accessibilityEvent ->
-                            accessibilityEvent.getEventType() == TYPE_WINDOW_STATE_CHANGED,
-                    () -> "Unable to click object to launch split",
-                    "Click launcher object to launch split");
+                    "clicking the launchable");
 
             try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer("clicked")) {
                 mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, OverviewTask.SPLIT_START_EVENT);
diff --git a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
index 501c4c3..200f2ff 100644
--- a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
+++ b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
@@ -130,8 +130,11 @@
             int endX = startX;
             int endY = startY - taskbarFromNavThreshold;
 
-            mLauncher.linearGesture(startX, startY, endX, endY, 10, /* slowDown= */ true,
-                    LauncherInstrumentation.GestureScope.EXPECT_PILFER);
+            mLauncher.executeAndWaitForLauncherStop(
+                    () -> mLauncher.linearGesture(startX, startY, endX, endY, 10,
+                            /* slowDown= */ true,
+                            LauncherInstrumentation.GestureScope.EXPECT_PILFER),
+                    "swiping");
             LauncherInstrumentation.log("swipeUpToUnstashTaskbar: sent linear swipe up gesture");
 
             return new Taskbar(mLauncher);
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 91ef472..e359cc8 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -21,6 +21,7 @@
 import static android.content.pm.PackageManager.MATCH_ALL;
 import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
 import static android.view.KeyEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_SCROLL;
 import static android.view.MotionEvent.ACTION_UP;
 import static android.view.MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT;
 
@@ -277,21 +278,35 @@
         assertNotNull("Cannot find content provider for " + testProviderAuthority, pi);
         ComponentName cn = new ComponentName(pi.packageName, pi.name);
 
+        final int iterations = isLauncherTest ? 300 : 100;
+
         if (pm.getComponentEnabledSetting(cn) != COMPONENT_ENABLED_STATE_ENABLED) {
             if (TestHelpers.isInLauncherProcess()) {
                 pm.setComponentEnabledSetting(cn, COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP);
             } else {
                 try {
                     final int userId = getContext().getUserId();
+                    final String launcherPidCommand = "pidof " + pi.packageName;
+                    final String initialPid = mDevice.executeShellCommand(launcherPidCommand);
+
                     mDevice.executeShellCommand(
                             "pm enable --user " + userId + " " + cn.flattenToString());
+
+                    // Wait for Launcher restart after enabling test provider.
+                    for (int i = 0; i < iterations; ++i) {
+                        final String currentPid = mDevice.executeShellCommand(launcherPidCommand)
+                                .replaceAll("\\s", "");
+                        if (!currentPid.isEmpty() && !currentPid.equals(initialPid)) break;
+                        if (i == iterations - 1) {
+                            fail("Launcher didn't restart after enabling test provider");
+                        }
+                        SystemClock.sleep(100);
+                    }
                 } catch (IOException e) {
                     fail(e.toString());
                 }
             }
 
-            final int iterations = isLauncherTest ? 300 : 100;
-
             // Wait for Launcher content provider to become enabled.
             for (int i = 0; i < iterations; ++i) {
                 final ContentProviderClient testProvider = getContext().getContentResolver()
@@ -370,8 +385,8 @@
                 .getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD);
     }
 
-    Insets getImeInsets() {
-        return getTestInfo(TestProtocol.REQUEST_IME_INSETS)
+    Insets getSystemGestureRegion() {
+        return getTestInfo(TestProtocol.REQUEST_SYSTEM_GESTURE_REGION)
                 .getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD);
     }
 
@@ -405,6 +420,11 @@
                 .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
     }
 
+    public int getOverviewCurrentPageIndex() {
+        return getTestInfo(TestProtocol.REQUEST_GET_OVERVIEW_CURRENT_PAGE_INDEX)
+                .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+    }
+
     float getExactScreenCenterX() {
         return getRealDisplaySize().x / 2f;
     }
@@ -1007,11 +1027,11 @@
             return;
         }
 
-        linearGesture(
-                displaySize.x / 2, displaySize.y - 1,
-                displaySize.x / 2, 0,
-                ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME,
-                false, GestureScope.EXPECT_PILFER);
+        if (isLauncher3()) {
+            gestureToDismissPopup(displaySize);
+        } else {
+            runToState(() -> gestureToDismissPopup(displaySize), NORMAL_STATE_ORDINAL, "swiping");
+        }
 
         try (LauncherInstrumentation.Closable c1 = addContextLayer(
                 String.format("Swiped up from floating view %s to home", floatingRes.get()))) {
@@ -1020,6 +1040,14 @@
         }
     }
 
+    private void gestureToDismissPopup(Point displaySize) {
+        linearGesture(
+                displaySize.x / 2, displaySize.y - 1,
+                displaySize.x / 2, 0,
+                ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME,
+                false, GestureScope.EXPECT_PILFER);
+    }
+
     /**
      * @return the Workspace object.
      * @deprecated use goHome().
@@ -1529,7 +1557,8 @@
         }
     }
 
-    void runToState(Runnable command, int expectedState, String actionName) {
+    /** Run an action and wait for the specified Launcher state. */
+    public void runToState(Runnable command, int expectedState, String actionName) {
         final List<Integer> actualEvents = new ArrayList<>();
         executeAndWaitForLauncherEvent(
                 command,
@@ -1702,6 +1731,16 @@
                 "scrolling");
     }
 
+    void pointerScroll(float pointerX, float pointerY, Direction direction) {
+        executeAndWaitForLauncherEvent(
+                () -> injectEvent(getPointerMotionEvent(
+                        ACTION_SCROLL, pointerX, pointerY, direction)),
+                event -> TestProtocol.SCROLL_FINISHED_MESSAGE.equals(event.getClassName()),
+                () -> "Didn't receive a scroll end message: " + direction + " scroll from ("
+                        + pointerX + ", " + pointerY + ")",
+                "scrolling");
+    }
+
     // Inject a swipe gesture. Inject exactly 'steps' motion points, incrementing event time by a
     // fixed interval each time.
     public void linearGesture(int startX, int startY, int endX, int endY, int steps,
@@ -1765,6 +1804,41 @@
         return getContext().getResources();
     }
 
+    private static MotionEvent getPointerMotionEvent(
+            int action, float x, float y, Direction direction) {
+        MotionEvent.PointerCoords[] coordinates = new MotionEvent.PointerCoords[1];
+        coordinates[0] = new MotionEvent.PointerCoords();
+        coordinates[0].x = x;
+        coordinates[0].y = y;
+        boolean isVertical = direction == Direction.UP || direction == Direction.DOWN;
+        boolean isForward = direction == Direction.RIGHT || direction == Direction.DOWN;
+        coordinates[0].setAxisValue(
+                isVertical ? MotionEvent.AXIS_VSCROLL : MotionEvent.AXIS_HSCROLL,
+                isForward ? 1f : -1f);
+
+        MotionEvent.PointerProperties[] properties = new MotionEvent.PointerProperties[1];
+        properties[0] = new MotionEvent.PointerProperties();
+        properties[0].id = 0;
+        properties[0].toolType = MotionEvent.TOOL_TYPE_MOUSE;
+
+        final long downTime = SystemClock.uptimeMillis();
+        return MotionEvent.obtain(
+                downTime,
+                downTime,
+                action,
+                /* pointerCount= */ 1,
+                properties,
+                coordinates,
+                /* metaState= */ 0,
+                /* buttonState= */ 0,
+                /* xPrecision= */ 1f,
+                /* yPrecision= */ 1f,
+                /* deviceId= */ 0,
+                /* edgeFlags= */ 0,
+                InputDevice.SOURCE_CLASS_POINTER,
+                /* flags= */ 0);
+    }
+
     private static MotionEvent getTrackpadMotionEvent(long downTime, long eventTime,
             int action, float x, float y, int pointerCount, TrackpadGestureType gestureType) {
         MotionEvent.PointerProperties[] pointerProperties =
@@ -2094,6 +2168,11 @@
         getTestInfo(TestProtocol.REQUEST_UNSTASH_TASKBAR_IF_STASHED);
     }
 
+    /** Shows the bubble bar if it is stashed, otherwise this does nothing. */
+    public void showBubbleBarIfHidden() {
+        getTestInfo(TestProtocol.REQUEST_UNSTASH_BUBBLE_BAR_IF_STASHED);
+    }
+
     /** Blocks the taskbar from automatically stashing based on time. */
     public void enableBlockTimeout(boolean enable) {
         getTestInfo(enable
@@ -2287,12 +2366,13 @@
                         : containerBounds.left - 1;
             }
             // If IME is visible and overlaps the container bounds, touch above it.
+            final Insets systemGestureRegion = getSystemGestureRegion();
             int bottomBound = Math.min(
                     containerBounds.bottom,
-                    getRealDisplaySize().y - getImeInsets().bottom);
-            int y = (bottomBound + containerBounds.top) / 2;
+                    getRealDisplaySize().y - systemGestureRegion.bottom);
+            int y = (bottomBound - containerBounds.top) / 2;
             // Do not tap in the status bar.
-            y = Math.max(y, getWindowInsets().top);
+            y = Math.max(y, systemGestureRegion.top);
 
             final long downTime = SystemClock.uptimeMillis();
             final Point tapTarget = new Point(x, y);
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewActions.java b/tests/tapl/com/android/launcher3/tapl/OverviewActions.java
index bd2c9c1..d7c40a0 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewActions.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewActions.java
@@ -89,8 +89,7 @@
     private SelectModeButtons getSelectModeButtons() {
         try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                 "want to get select mode buttons")) {
-            UiObject2 selectModeButtons = mLauncher.waitForLauncherObject("select_mode_buttons");
-            return new SelectModeButtons(selectModeButtons, mLauncher);
+            return new SelectModeButtons(mLauncher);
         }
     }
 
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java b/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java
index 3d2914d..902ad5b 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java
@@ -16,6 +16,9 @@
 
 package com.android.launcher3.tapl;
 
+import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_MODAL_TASK_STATE_ORDINAL;
+import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_SPLIT_SELECT_ORDINAL;
+
 import androidx.annotation.NonNull;
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.UiObject2;
@@ -40,8 +43,11 @@
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                      "tap split menu item")) {
-            mLauncher.clickLauncherObject(
-                    mLauncher.findObjectInContainer(mMenu, By.textStartsWith("Split")));
+            mLauncher.runToState(() -> mLauncher.clickLauncherObject(
+                            mLauncher.findObjectInContainer(mMenu, By.textStartsWith("Split"))),
+                    OVERVIEW_SPLIT_SELECT_ORDINAL,
+                    "tapping split menu item"
+            );
 
             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
                     "tapped split menu item")) {
@@ -72,6 +78,25 @@
         }
     }
 
+    /** Taps the select menu item from the overview task menu. */
+    @NonNull
+    public SelectModeButtons tapSelectMenuItem() {
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+             LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                     "before tapping the select menu item")) {
+
+            mLauncher.runToState(
+                    () -> mLauncher.clickLauncherObject(
+                            mLauncher.findObjectInContainer(mMenu, By.text("Select"))),
+                    OVERVIEW_MODAL_TASK_STATE_ORDINAL, "tapping select menu item");
+
+            try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+                    "select menu item opened")) {
+                return new SelectModeButtons(mLauncher);
+            }
+        }
+    }
+
     /** Returns true if an item matching the given string is present in the menu. */
     public boolean hasMenuItem(String expectedMenuItemText) {
         UiObject2 menuItem = mLauncher.findObjectInContainer(mMenu, By.text(expectedMenuItemText));
diff --git a/tests/tapl/com/android/launcher3/tapl/Qsb.java b/tests/tapl/com/android/launcher3/tapl/Qsb.java
index fe2a63d..d67b8a3 100644
--- a/tests/tapl/com/android/launcher3/tapl/Qsb.java
+++ b/tests/tapl/com/android/launcher3/tapl/Qsb.java
@@ -118,9 +118,7 @@
         try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                 "want to open search result page");
              LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
-            mLauncher.clickLauncherObject(waitForQsbObject());
-            // wait for the result rendering to complete
-            mLauncher.waitForIdle();
+            clickQsb();
             try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer(
                     "clicked qsb to open search result page")) {
                 return createSearchResult();
@@ -128,6 +126,10 @@
         }
     }
 
+    protected void clickQsb() {
+        mLauncher.clickLauncherObject(waitForQsbObject());
+    }
+
     @Override
     public LauncherInstrumentation getLauncher() {
         return mLauncher;
diff --git a/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java b/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
index f0a8aa2..8d3a631 100644
--- a/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
+++ b/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.tapl;
 
+import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
+
 import android.widget.TextView;
 
 import androidx.test.uiautomator.By;
@@ -39,7 +41,7 @@
 
     /** Find the app from search results with app name. */
     public AppIcon findAppIcon(String appName) {
-        UiObject2 icon = mLauncher.waitForLauncherObject(By.clazz(TextView.class).text(appName));
+        UiObject2 icon = mLauncher.waitForLauncherObject(AppIcon.getAppIconSelector(appName));
         return createAppIcon(icon);
     }
 
@@ -87,7 +89,7 @@
                              + (tapRight ? "right" : "left"))) {
             final UiObject2 allAppsBottomSheet =
                     mLauncher.waitForLauncherObject(BOTTOM_SHEET_RES_ID);
-            mLauncher.touchOutsideContainer(allAppsBottomSheet, tapRight);
+            tapOutside(tapRight, allAppsBottomSheet);
             try (LauncherInstrumentation.Closable tapped = mLauncher.addContextLayer(
                     "tapped outside AllApps bottom sheet")) {
                 verifyVisibleContainerOnDismiss();
@@ -95,6 +97,13 @@
         }
     }
 
+    protected void tapOutside(boolean tapRight, UiObject2 allAppsBottomSheet) {
+        mLauncher.runToState(
+                () -> mLauncher.touchOutsideContainer(allAppsBottomSheet, tapRight),
+                NORMAL_STATE_ORDINAL,
+                "tappig outside");
+    }
+
     protected void verifyVisibleContainerOnDismiss() {
         mLauncher.getWorkspace();
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/SearchResultFromTaskbarQsb.java b/tests/tapl/com/android/launcher3/tapl/SearchResultFromTaskbarQsb.java
index 00291a3..f4b4a91 100644
--- a/tests/tapl/com/android/launcher3/tapl/SearchResultFromTaskbarQsb.java
+++ b/tests/tapl/com/android/launcher3/tapl/SearchResultFromTaskbarQsb.java
@@ -50,4 +50,9 @@
     protected void verifyVisibleContainerOnDismiss() {
         mLauncher.getLaunchedAppState().assertTaskbarVisible();
     }
+
+    @Override
+    protected void tapOutside(boolean tapRight, UiObject2 allAppsBottomSheet) {
+        mLauncher.touchOutsideContainer(allAppsBottomSheet, tapRight);
+    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/SelectModeButtons.java b/tests/tapl/com/android/launcher3/tapl/SelectModeButtons.java
index e1b73a4..e2bc17b 100644
--- a/tests/tapl/com/android/launcher3/tapl/SelectModeButtons.java
+++ b/tests/tapl/com/android/launcher3/tapl/SelectModeButtons.java
@@ -16,9 +16,17 @@
 
 package com.android.launcher3.tapl;
 
+import static android.view.KeyEvent.KEYCODE_ESCAPE;
+
+import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_STATE_ORDINAL;
+
 import androidx.annotation.NonNull;
 import androidx.test.uiautomator.UiObject2;
 
+import com.android.launcher3.testing.shared.TestProtocol;
+
+import java.util.regex.Pattern;
+
 /**
  * View containing select mode buttons
  */
@@ -26,9 +34,14 @@
     private final UiObject2 mSelectModeButtons;
     private final LauncherInstrumentation mLauncher;
 
-    SelectModeButtons(UiObject2 selectModeButtons,
-            LauncherInstrumentation launcherInstrumentation) {
-        mSelectModeButtons = selectModeButtons;
+    private static final Pattern EVENT_ALT_ESC_DOWN = Pattern.compile(
+            "Key event: KeyEvent.*?action=ACTION_DOWN.*?keyCode=KEYCODE_ESCAPE.*?metaState=0");
+    private static final Pattern EVENT_ALT_ESC_UP = Pattern.compile(
+            "Key event: KeyEvent.*?action=ACTION_UP.*?keyCode=KEYCODE_ESCAPE.*?metaState=0");
+
+
+    SelectModeButtons(LauncherInstrumentation launcherInstrumentation) {
+        mSelectModeButtons = launcherInstrumentation.waitForLauncherObject("select_mode_buttons");
         mLauncher = launcherInstrumentation;
     }
 
@@ -48,4 +61,25 @@
             }
         }
     }
+
+    /**
+     * Close select mode when ESC key is pressed.
+     * @return The Overview
+     */
+    @NonNull
+    public Overview dismissByEscKey() {
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+            mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_ESC_DOWN);
+            mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_ESC_UP);
+            mLauncher.runToState(
+                    () -> mLauncher.getDevice().pressKeyCode(KEYCODE_ESCAPE),
+                    OVERVIEW_STATE_ORDINAL,
+                    "pressing Esc");
+            try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                    "pressed esc key")) {
+                return new Overview(mLauncher);
+            }
+        }
+    }
+
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/TaskbarAppIcon.java b/tests/tapl/com/android/launcher3/tapl/TaskbarAppIcon.java
index 064f80c..d05c112 100644
--- a/tests/tapl/com/android/launcher3/tapl/TaskbarAppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/TaskbarAppIcon.java
@@ -68,6 +68,7 @@
 
     @Override
     protected boolean launcherStopsAfterLaunch() {
+        // false because if taskbar is showing then launcher is already stopped.
         return false;
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java
index 105bc3b..6387b05 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widgets.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java
@@ -19,6 +19,7 @@
 import static com.android.launcher3.tapl.LauncherInstrumentation.WAIT_TIME_MS;
 import static com.android.launcher3.tapl.LauncherInstrumentation.log;
 
+import android.annotation.Nullable;
 import android.graphics.Rect;
 
 import androidx.test.uiautomator.By;
@@ -114,7 +115,13 @@
                 .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
     }
 
+    /** Get widget with supplied text. */
     public Widget getWidget(String labelText) {
+        return getWidget(labelText, null);
+    }
+
+    /** Get widget with supplied text and app package */
+    public Widget getWidget(String labelText, @Nullable String testAppWidgetPackage) {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                      "getting widget " + labelText + " in widgets list")) {
@@ -124,7 +131,8 @@
             mLauncher.assertTrue("Widgets container didn't become scrollable",
                     fullWidgetsPicker.wait(Until.scrollable(true), WAIT_TIME_MS));
 
-            final UiObject2 widgetsContainer = findTestAppWidgetsTableContainer();
+            final UiObject2 widgetsContainer =
+                    findTestAppWidgetsTableContainer(testAppWidgetPackage);
             mLauncher.assertTrue("Can't locate widgets list for the test app: "
                             + mLauncher.getLauncherPackageName(),
                     widgetsContainer != null);
@@ -180,14 +188,22 @@
         return searchBar;
     }
 
-    /** Finds the widgets list of this test app from the collapsed full widgets picker. */
-    private UiObject2 findTestAppWidgetsTableContainer() {
+    /**
+     * Finds the widgets list of this test app or supplied test app package from the collapsed full
+     * widgets picker.
+     */
+    private UiObject2 findTestAppWidgetsTableContainer(@Nullable String testAppWidgetPackage) {
         final BySelector headerSelector = By.res(mLauncher.getLauncherPackageName(),
                 "widgets_list_header");
         final BySelector widgetPickerSelector = By.res(mLauncher.getLauncherPackageName(),
                 "container");
-        final BySelector targetAppSelector = By.clazz("android.widget.TextView").text(
-                mLauncher.getContext().getPackageName());
+
+        String packageName =  mLauncher.getContext().getPackageName();
+        final BySelector targetAppSelector = By
+                .clazz("android.widget.TextView")
+                .text((testAppWidgetPackage == null || testAppWidgetPackage.isEmpty())
+                                ? packageName
+                                : testAppWidgetPackage);
         final BySelector widgetsContainerSelector = By.res(mLauncher.getLauncherPackageName(),
                 "widgets_table");
 
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index f8fa00c..a911de4 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -121,7 +121,10 @@
              LauncherInstrumentation.Closable c =
                      mLauncher.addContextLayer("want to open all apps search")) {
             verifyActiveContainer();
-            mLauncher.getDevice().pressKeyCode(KEYCODE_META_RIGHT);
+            mLauncher.runToState(
+                    () -> mLauncher.getDevice().pressKeyCode(KEYCODE_META_RIGHT),
+                    ALL_APPS_STATE_ORDINAL,
+                    "pressing keyboard shortcut");
             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
                     "pressed meta key")) {
                 return new HomeAllApps(mLauncher);
@@ -192,16 +195,24 @@
     }
 
     /**
-     * Ensures that workspace is scrollable. If it's not, drags an icon icons from hotseat to the
-     * second screen.
+     * Ensures that workspace is scrollable. If it's not, drags a chrome app icon from hotseat
+     * to the second screen.
      */
     public void ensureWorkspaceIsScrollable() {
+        ensureWorkspaceIsScrollable("Chrome");
+    }
+
+    /**
+     * Ensures that workspace is scrollable. If it's not, drags an icon of a given app name from
+     * hotseat to the second screen.
+     */
+    public void ensureWorkspaceIsScrollable(String appName) {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
             final UiObject2 workspace = verifyActiveContainer();
             if (!isWorkspaceScrollable(workspace)) {
                 try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                         "dragging icon to a second page of workspace to make it scrollable")) {
-                    dragIcon(workspace, getHotseatAppIcon("Chrome"), pagesPerScreen());
+                    dragIcon(workspace, getHotseatAppIcon(appName), pagesPerScreen());
                     verifyActiveContainer();
                 }
             }
@@ -450,7 +461,12 @@
     }
 
     /** Returns the index of the current page */
-    private static int geCurrentPage(LauncherInstrumentation launcher) {
+    public int getCurrentPage() {
+        return getCurrentPage(mLauncher);
+    }
+
+    /** Returns the index of the current page */
+    private static int getCurrentPage(LauncherInstrumentation launcher) {
         return launcher.getTestInfo(TestProtocol.REQUEST_WORKSPACE_CURRENT_PAGE_INDEX).getInt(
                 TestProtocol.TEST_INFO_RESPONSE_FIELD);
     }
@@ -637,7 +653,7 @@
             Point currentPosition, int destinationWorkspaceIndex, int y) {
         final long downTime = SystemClock.uptimeMillis();
         int displayX = launcher.getRealDisplaySize().x;
-        int currentPage = Workspace.geCurrentPage(launcher);
+        int currentPage = Workspace.getCurrentPage(launcher);
         int counter = 0;
         while (currentPage != destinationWorkspaceIndex) {
             counter++;
@@ -656,7 +672,7 @@
                     () -> launcher.movePointer(finalDragStart, screenEdge, DEFAULT_DRAG_STEPS,
                             true, downTime, downTime, true,
                             LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER));
-            currentPage = Workspace.geCurrentPage(launcher);
+            currentPage = Workspace.getCurrentPage(launcher);
             currentPosition = screenEdge;
         }
         return currentPosition;
@@ -695,10 +711,9 @@
      */
     public void flingForward() {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
-            final UiObject2 workspace = verifyActiveContainer();
-            mLauncher.scroll(workspace, Direction.RIGHT,
-                    new Rect(0, 0, mLauncher.getEdgeSensitivityWidth() + 1, 0),
-                    FLING_STEPS, false);
+            Rect workspaceBounds = mLauncher.getVisibleBounds(verifyActiveContainer());
+            mLauncher.pointerScroll(
+                    workspaceBounds.centerX(), workspaceBounds.centerY(), Direction.RIGHT);
             verifyActiveContainer();
         }
     }
@@ -709,10 +724,9 @@
      */
     public void flingBackward() {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
-            final UiObject2 workspace = verifyActiveContainer();
-            mLauncher.scroll(workspace, Direction.LEFT,
-                    new Rect(mLauncher.getEdgeSensitivityWidth() + 1, 0, 0, 0),
-                    FLING_STEPS, false);
+            Rect workspaceBounds = mLauncher.getVisibleBounds(verifyActiveContainer());
+            mLauncher.pointerScroll(
+                    workspaceBounds.centerX(), workspaceBounds.centerY(), Direction.LEFT);
             verifyActiveContainer();
         }
     }