Merge "Do not recreate taskbar due to unestimated bounds" into main
diff --git a/Android.bp b/Android.bp
index 010ea45..19d2a58 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,9 +109,12 @@
     srcs: [
         "tests/tapl/**/*.java",
     ],
-    resource_dirs: [ ],
+    resource_dirs: [],
     manifest: "tests/tapl/AndroidManifest.xml",
     platform_apis: true,
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 java_library_static {
@@ -99,12 +126,15 @@
     sdk_version: "current",
     proto: {
         type: "lite",
-        local_include_dirs:[
+        local_include_dirs: [
             "protos",
             "protos_overrides",
         ],
     },
     static_libs: ["libprotobuf-java-lite"],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 java_library_static {
@@ -115,14 +145,17 @@
     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",
+    ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 java_library {
@@ -134,12 +167,15 @@
 
     sdk_version: "current",
     min_sdk_version: min_launcher3_sdk_version,
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 // Library with all the dependencies for building Launcher3
 android_library {
     name: "Launcher3ResLib",
-    srcs: [ ],
+    srcs: [],
     resource_dirs: ["res"],
     static_libs: [
         "LauncherPluginLib",
@@ -154,7 +190,7 @@
         "com.google.android.material_material",
         "iconloader_base",
         "view_capture",
-        "animationlib"
+        "animationlib",
     ],
     manifest: "AndroidManifest-common.xml",
     sdk_version: "current",
@@ -236,7 +272,7 @@
 // Library with all the dependencies for building quickstep
 android_library {
     name: "QuickstepResLib",
-    srcs: [ ],
+    srcs: [],
     resource_dirs: [
         "quickstep/res",
     ],
@@ -253,9 +289,11 @@
     ],
     manifest: "quickstep/AndroidManifest.xml",
     min_sdk_version: "current",
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
-
 // Library with all the dependencies for building Launcher Go
 android_library {
     name: "LauncherGoResLib",
@@ -360,7 +398,10 @@
     manifest: "go/AndroidManifest.xml",
     jacoco: {
         include_filter: ["com.android.launcher3.*"],
-    }
+    },
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 
 }
 
@@ -396,7 +437,10 @@
     manifest: "quickstep/AndroidManifest.xml",
     jacoco: {
         include_filter: ["com.android.launcher3.*"],
-    }
+    },
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 
 }
 
@@ -414,7 +458,7 @@
     min_sdk_version: "current",
     target_sdk_version: "current",
 
-    srcs: [ ],
+    srcs: [],
 
     resource_dirs: [
         "go/quickstep/res",
@@ -446,7 +490,9 @@
     manifest: "quickstep/AndroidManifest.xml",
     jacoco: {
         include_filter: ["com.android.launcher3.*"],
-    }
+    },
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 
 }
-
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/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/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..6e88780 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
@@ -117,9 +117,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,10 +137,7 @@
         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;
         }
@@ -146,7 +146,7 @@
             UI_HELPER_EXECUTOR.execute(() ->
                     SystemUiProxy.INSTANCE.get(mKeyboardQuickSwitchView.getContext())
                             .showDesktopApp(task.task1.key.id));
-        } else if (task2 == null) {
+        } else if (task.task2 == null) {
             UI_HELPER_EXECUTOR.execute(() ->
                     ActivityManagerWrapper.getInstance().startActivityFromRecents(
                             task.task1.key,
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index fb25ec1..eff6e27 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;
@@ -130,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
@@ -1147,15 +1152,25 @@
                     @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 +1182,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);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 5dfef97..a2e5e81 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -340,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;
         }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index b3a8b93..2f7f6f3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -21,7 +21,9 @@
 import static com.android.app.animation.Interpolators.FINAL_FRAME;
 import static com.android.app.animation.Interpolators.INSTANT;
 import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
+import static com.android.launcher3.config.FeatureFlags.enableTaskbarPinning;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TRANSIENT_TASKBAR_HIDE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TRANSIENT_TASKBAR_SHOW;
 import static com.android.launcher3.taskbar.TaskbarKeyguardController.MASK_ANY_SYSUI_LOCKED;
@@ -57,6 +59,7 @@
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.launcher3.Alarm;
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.AnimatedFloat;
 import com.android.launcher3.anim.AnimatorListeners;
@@ -930,19 +933,27 @@
     }
 
     /**
-     * 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() && enableTaskbarPinning()
+                && LauncherPrefs.get(mActivity).get(TASKBAR_PINNING)) {
+            return false;
+        }
+        return mIsImeShowing || mIsImeSwitcherShowing;
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
index 7c24ba8..0f88aac 100644
--- a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
+++ b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
@@ -287,7 +287,7 @@
         mBackInProgress = true;
         RemoteAnimationTarget appTarget = backEvent.getDepartingAnimationTarget();
 
-        if (appTarget == null) {
+        if (appTarget == null || appTarget.leash == null || !appTarget.leash.isValid()) {
             return;
         }
 
@@ -456,6 +456,7 @@
                     mBackInProgress /* fromPredictiveBack */);
         startTransitionAnimations(pair.first, pair.second);
         mLauncher.clearForceInvisibleFlag(INVISIBLE_ALL);
+        customizeStatusBarAppearance(true);
     }
 
     private void finishAnimation() {
diff --git a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 0303791..9d942c5 100644
--- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -149,6 +149,11 @@
             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;
         }
 
         return super.call(method, arg, extras);
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index ca9d13e..ebb6ba8 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
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/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/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/dimens.xml b/res/values/dimens.xml
index 62ce256..0ebcbf3 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -56,7 +56,7 @@
     <!-- App Widget resize frame -->
     <dimen name="widget_handle_margin">13dp</dimen>
     <dimen name="resize_frame_background_padding">24dp</dimen>
-    <dimen name="resize_frame_margin">22dp</dimen>
+    <dimen name="resize_frame_margin">23dp</dimen>
     <dimen name="resize_frame_invalid_drag_across_two_panel_opacity_margin">24dp</dimen>
 
     <!-- App widget reconfigure button -->
@@ -155,6 +155,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>
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 1ca7da9..53297f2 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -789,14 +789,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;
         }
     }
 
@@ -1074,11 +1076,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 +1090,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);
     }
 
@@ -1788,7 +1786,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 +1826,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/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/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index 87437bd..8301981 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -745,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/tests/Android.bp b/tests/Android.bp
index bdb53ba..dd0ba9e 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -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/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt
index 1781673..0f27893 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt
@@ -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..85f7ca1 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape3Button.txt
@@ -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..bd47777 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait.txt
@@ -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..902885a 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait3Button.txt
@@ -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/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/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/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 c9d118f..6d00253 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
@@ -30,7 +30,7 @@
 
 /** Unit test for [ExecutorRunnable] */
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class ExecutorRunnableTest {
 
     private lateinit var underTest: ExecutorRunnable<Int>
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/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index f8fa00c..75d6ed1 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -192,16 +192,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 +458,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 +650,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 +669,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;