Merge "Double pre-inflate counts if work profile is enabled" into main
diff --git a/Android.bp b/Android.bp
index 28eee94..6cd559b 100644
--- a/Android.bp
+++ b/Android.bp
@@ -175,6 +175,7 @@
         "launcher-testing-shared",
         "animationlib",
         "com_android_launcher3_flags_lib",
+        "com_android_wm_shell_flags_lib",
     ],
     sdk_version: "current",
     min_sdk_version: min_launcher3_sdk_version,
diff --git a/OWNERS b/OWNERS
index 7834396..353ac8e 100644
--- a/OWNERS
+++ b/OWNERS
@@ -15,6 +15,7 @@
 alexchau@google.com
 patmanning@google.com
 tsuharesu@google.com
+awickham@google.com
 
 per-file FeatureFlags.java, globs = set noparent
 per-file FeatureFlags.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com, captaincole@google.com
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index a77791f..908ec0b 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,4 +1,6 @@
 [Hook Scripts]
 checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --config_xml tools/checkstyle.xml --sha ${PREUPLOAD_COMMIT}
 
-ktfmt_hook = ${REPO_ROOT}/external/ktfmt/ktfmt.py --check ${PREUPLOAD_FILES}
\ No newline at end of file
+ktfmt_hook = ${REPO_ROOT}/external/ktfmt/ktfmt.py --check ${PREUPLOAD_FILES}
+
+flag_hook = ${REPO_ROOT}/vendor/unbundled_google/packages/NexusLauncher/flag_check.py ${PREUPLOAD_COMMIT_MESSAGE}
diff --git a/go/src/com/android/launcher3/model/LauncherBinder.java b/go/src/com/android/launcher3/model/LauncherBinder.java
index 437d8ca..7a0dce8 100644
--- a/go/src/com/android/launcher3/model/LauncherBinder.java
+++ b/go/src/com/android/launcher3/model/LauncherBinder.java
@@ -38,4 +38,8 @@
     @Override
     public void bindWidgets() {
     }
+
+    @Override
+    public void bindSmartspaceWidget() {
+    }
 }
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index 7c0a5ae..6d958ed 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -95,10 +95,11 @@
         </provider>
 
         <activity android:name="com.android.launcher3.proxy.ProxyActivityStarter"
-             android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen"
-             android:launchMode="singleTask"
-             android:clearTaskOnLaunch="true"
-             android:exported="false"/>
+            android:theme="@style/ProxyActivityStarterTheme"
+            android:launchMode="singleTask"
+            android:clearTaskOnLaunch="true"
+            android:exported="false"
+            />
 
         <activity android:name="com.android.quickstep.interaction.GestureSandboxActivity"
             android:autoRemoveFromRecents="true"
diff --git a/quickstep/res/values-eu/strings.xml b/quickstep/res/values-eu/strings.xml
index 0d6c664..596802f 100644
--- a/quickstep/res/values-eu/strings.xml
+++ b/quickstep/res/values-eu/strings.xml
@@ -24,7 +24,7 @@
     <string name="recents_empty_message" msgid="7040467240571714191">"Ez dago azkenaldi honetako ezer"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Aplikazioen erabileraren ezarpenak"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Garbitu guztiak"</string>
-    <string name="accessibility_recent_apps" msgid="4058661986695117371">"Azken aplikazioak"</string>
+    <string name="accessibility_recent_apps" msgid="4058661986695117371">"Azkenaldiko aplikazioak"</string>
     <string name="task_view_closed" msgid="9170038230110856166">"Itxi da zeregina"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g> (<xliff:g id="REMAINING_TIME">%2$s</xliff:g>)"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 min"</string>
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index ce644dc..cca0fd4 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -56,6 +56,7 @@
 import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY;
 import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
 import static com.android.launcher3.util.DisplayController.isTransientTaskbar;
+import static com.android.launcher3.util.Executors.ORDERED_BG_EXECUTOR;
 import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
 import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
 import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
@@ -78,6 +79,7 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
+import android.database.ContentObserver;
 import android.graphics.Color;
 import android.graphics.Matrix;
 import android.graphics.Point;
@@ -92,6 +94,7 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.provider.Settings.Global;
 import android.util.Pair;
 import android.util.Size;
 import android.view.CrossWindowBlurListeners;
@@ -231,6 +234,16 @@
 
     private final StartingWindowListener mStartingWindowListener =
             new StartingWindowListener(this);
+    private ContentObserver mAnimationRemovalObserver = new ContentObserver(
+            ORDERED_BG_EXECUTOR.getHandler()) {
+        @Override
+        public void onChange(boolean selfChange) {
+            mAreAnimationsEnabled = Global.getFloat(mLauncher.getContentResolver(),
+                    Global.ANIMATOR_DURATION_SCALE, 1f) > 0
+                    || Global.getFloat(mLauncher.getContentResolver(),
+                    Global.TRANSITION_ANIMATION_SCALE, 1f) > 0;
+        }
+    };;
 
     private DeviceProfile mDeviceProfile;
 
@@ -260,6 +273,7 @@
     // Pairs of window starting type and starting window background color for starting tasks
     // Will never be larger than MAX_NUM_TASKS
     private LinkedHashMap<Integer, Pair<Integer, Integer>> mTaskStartParams;
+    private boolean mAreAnimationsEnabled = true;
 
     private final Interpolator mOpeningXInterpolator;
     private final Interpolator mOpeningInterpolator;
@@ -270,6 +284,7 @@
         mHandler = new Handler(Looper.getMainLooper());
         mDeviceProfile = mLauncher.getDeviceProfile();
         mBackAnimationController = new LauncherBackAnimationController(mLauncher, this);
+        checkAndMonitorIfAnimationsAreEnabled();
 
         Resources res = mLauncher.getResources();
         mClosingWindowTransY = res.getDimensionPixelSize(R.dimen.closing_window_trans_y);
@@ -1160,6 +1175,8 @@
         unregisterRemoteAnimations();
         unregisterRemoteTransitions();
         SystemUiProxy.INSTANCE.get(mLauncher).setStartingWindowListener(null);
+        ORDERED_BG_EXECUTOR.execute(() -> mLauncher.getContentResolver()
+                .unregisterContentObserver(mAnimationRemovalObserver));
     }
 
     private void unregisterRemoteAnimations() {
@@ -1197,6 +1214,17 @@
         }
     }
 
+    private void checkAndMonitorIfAnimationsAreEnabled() {
+        ORDERED_BG_EXECUTOR.execute(() -> {
+            mAnimationRemovalObserver.onChange(true);
+            mLauncher.getContentResolver().registerContentObserver(Global.getUriFor(
+                    Global.ANIMATOR_DURATION_SCALE), false, mAnimationRemovalObserver);
+            mLauncher.getContentResolver().registerContentObserver(Global.getUriFor(
+                    Global.TRANSITION_ANIMATION_SCALE), false, mAnimationRemovalObserver);
+
+        });
+    }
+
     private boolean launcherIsATargetWithMode(RemoteAnimationTarget[] targets, int mode) {
         for (RemoteAnimationTarget target : targets) {
             if (target.mode == mode && target.taskInfo != null
@@ -1375,7 +1403,7 @@
                     (LauncherAppWidgetHostView) launcherView, targetRect, windowSize,
                     mDeviceProfile.isMultiWindowMode ? 0 : getWindowCornerRadius(mLauncher),
                     isTransluscent, fallbackBackgroundColor);
-        } else if (launcherView != null) {
+        } else if (launcherView != null && mAreAnimationsEnabled) {
             floatingIconView = getFloatingIconView(mLauncher, launcherView, null,
                     mLauncher.getTaskbarUIController() == null
                             ? null
diff --git a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
index e8374b8..037f7a8 100644
--- a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
@@ -35,9 +35,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.FloatingHeaderRow;
 import com.android.launcher3.allapps.FloatingHeaderView;
-import com.android.launcher3.util.OnboardingPrefs;
 import com.android.launcher3.util.Themes;
-import com.android.launcher3.views.ActivityContext;
 
 /**
  * A view which shows a horizontal divider
@@ -93,10 +91,7 @@
                 ? R.color.all_apps_label_text_dark
                 : R.color.all_apps_label_text);
 
-        OnboardingPrefs<?> onboardingPrefs = ActivityContext.lookupContext(
-                getContext()).getOnboardingPrefs();
-        mShowAllAppsLabel = onboardingPrefs == null || !onboardingPrefs.hasReachedMaxCount(
-                ALL_APPS_VISITED_COUNT);
+        mShowAllAppsLabel = !ALL_APPS_VISITED_COUNT.hasReachedMax(context);
     }
 
     public void setup(FloatingHeaderView parent, FloatingHeaderRow[] rows, boolean tabsHidden) {
diff --git a/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt b/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
index 6fe007c..10733fb 100644
--- a/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
+++ b/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
@@ -98,6 +98,9 @@
             mergeTarget: IBinder,
             finishCallback: IRemoteTransitionFinishedCallback
         ) {}
+
+        override fun onTransitionConsumed(transition: IBinder?, aborted: Boolean) {
+        }
     }
 
     companion object {
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index a63f9e8..baea418 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -23,6 +23,7 @@
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_RANKED;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.FlagDebugUtils.appendFlag;
+import static com.android.launcher3.util.OnboardingPrefs.HOTSEAT_LONGPRESS_TIP_SEEN;
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
@@ -41,6 +42,7 @@
 import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget;
 import com.android.launcher3.Hotseat;
+import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.AnimationSuccessListener;
@@ -59,7 +61,6 @@
 import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.uioverrides.PredictedAppIcon;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
-import com.android.launcher3.util.OnboardingPrefs;
 import com.android.launcher3.views.Snackbar;
 
 import java.io.PrintWriter;
@@ -104,12 +105,11 @@
         if (mLauncher.getWorkspace().isSwitchingState()) return false;
 
         TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onWorkspaceItemLongClick");
-        if (mEnableHotseatLongPressTipForTesting && !mLauncher.getOnboardingPrefs().getBoolean(
-                OnboardingPrefs.HOTSEAT_LONGPRESS_TIP_SEEN)) {
+        if (mEnableHotseatLongPressTipForTesting && !HOTSEAT_LONGPRESS_TIP_SEEN.get(mLauncher)) {
             Snackbar.show(mLauncher, R.string.hotseat_tip_gaps_filled,
                     R.string.hotseat_prediction_settings, null,
                     () -> mLauncher.startActivity(getSettingsIntent()));
-            mLauncher.getOnboardingPrefs().markChecked(OnboardingPrefs.HOTSEAT_LONGPRESS_TIP_SEEN);
+            LauncherPrefs.get(mLauncher).put(HOTSEAT_LONGPRESS_TIP_SEEN, true);
             mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
             return true;
         }
diff --git a/quickstep/src/com/android/launcher3/model/PredictionHelper.java b/quickstep/src/com/android/launcher3/model/PredictionHelper.java
index 738dd83..dbd99e1 100644
--- a/quickstep/src/com/android/launcher3/model/PredictionHelper.java
+++ b/quickstep/src/com/android/launcher3/model/PredictionHelper.java
@@ -67,6 +67,9 @@
         } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
             return new AppTarget.Builder(new AppTargetId("folder:" + info.id),
                     context.getPackageName(), info.user).build();
+        } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR) {
+            return new AppTarget.Builder(new AppTargetId("app_pair:" + info.id),
+                    context.getPackageName(), info.user).build();
         }
         return null;
     }
diff --git a/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java b/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java
index e504141..2fcbe4e 100644
--- a/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java
+++ b/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java
@@ -15,8 +15,9 @@
  */
 package com.android.launcher3.model;
 
+import static com.android.launcher3.LauncherPrefs.nonRestorableItem;
+import static com.android.launcher3.EncryptionType.ENCRYPTED;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
-import static com.android.launcher3.model.QuickstepModelDelegate.LAST_PREDICTION_ENABLED_STATE;
 import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER;
 
 import android.app.prediction.AppTarget;
@@ -29,6 +30,7 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.launcher3.ConstantItem;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.model.BgDataModel.FixedContainerItems;
@@ -47,6 +49,9 @@
  */
 public class PredictionUpdateTask extends BaseModelUpdateTask {
 
+    public static final ConstantItem<Boolean> LAST_PREDICTION_ENABLED =
+            nonRestorableItem("last_prediction_enabled_state", true, ENCRYPTED);
+
     private final List<AppTarget> mTargets;
     private final PredictorState mPredictorState;
 
@@ -61,8 +66,7 @@
         Context context = app.getContext();
 
         // TODO: remove this
-        LauncherPrefs.getDevicePrefs(context).edit()
-                .putBoolean(LAST_PREDICTION_ENABLED_STATE, !mTargets.isEmpty()).apply();
+        LauncherPrefs.get(context).put(LAST_PREDICTION_ENABLED, !mTargets.isEmpty());
 
         Set<UserHandle> usersForChangedShortcuts =
                 dataModel.extraItems.get(mPredictorState.containerId).items.stream()
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index 32361a8..667f784 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -18,7 +18,8 @@
 import static android.text.format.DateUtils.DAY_IN_MILLIS;
 import static android.text.format.DateUtils.formatElapsedTime;
 
-import static com.android.launcher3.LauncherPrefs.getDevicePrefs;
+import static com.android.launcher3.LauncherPrefs.nonRestorableItem;
+import static com.android.launcher3.EncryptionType.ENCRYPTED;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
@@ -39,7 +40,6 @@
 import android.app.prediction.AppTargetEvent;
 import android.content.Context;
 import android.content.Intent;
-import android.content.SharedPreferences;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
 import android.content.pm.ShortcutInfo;
@@ -55,8 +55,10 @@
 import androidx.annotation.VisibleForTesting;
 import androidx.annotation.WorkerThread;
 
+import com.android.launcher3.ConstantItem;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.logging.InstanceId;
@@ -86,14 +88,15 @@
  */
 public class QuickstepModelDelegate extends ModelDelegate {
 
-    public static final String LAST_PREDICTION_ENABLED_STATE = "last_prediction_enabled_state";
-    private static final String LAST_SNAPSHOT_TIME_MILLIS = "LAST_SNAPSHOT_TIME_MILLIS";
     private static final String BUNDLE_KEY_ADDED_APP_WIDGETS = "added_app_widgets";
     private static final int NUM_OF_RECOMMENDED_WIDGETS_PREDICATION = 20;
 
     private static final boolean IS_DEBUG = false;
     private static final String TAG = "QuickstepModelDelegate";
 
+    private static final ConstantItem<Long> LAST_SNAPSHOT_TIME_MILLIS =
+            nonRestorableItem("LAST_SNAPSHOT_TIME_MILLIS", 0L, ENCRYPTED);
+
     @VisibleForTesting
     final PredictorState mAllAppsState =
             new PredictorState(CONTAINER_PREDICTION, "all_apps_predictions");
@@ -211,8 +214,8 @@
         super.modelLoadComplete();
 
         // Log snapshot of the model
-        SharedPreferences prefs = getDevicePrefs(mApp.getContext());
-        long lastSnapshotTimeMillis = prefs.getLong(LAST_SNAPSHOT_TIME_MILLIS, 0);
+        LauncherPrefs prefs = LauncherPrefs.get(mApp.getContext());
+        long lastSnapshotTimeMillis = prefs.get(LAST_SNAPSHOT_TIME_MILLIS);
         // Log snapshot only if previous snapshot was older than a day
         long now = System.currentTimeMillis();
         if (now - lastSnapshotTimeMillis < DAY_IN_MILLIS) {
@@ -233,7 +236,7 @@
                 StatsLogCompatManager.writeSnapshot(info.buildProto(parent), instanceId);
             }
             additionalSnapshotEvents(instanceId);
-            prefs.edit().putLong(LAST_SNAPSHOT_TIME_MILLIS, now).apply();
+            prefs.put(LAST_SNAPSHOT_TIME_MILLIS, now);
         }
 
         // Only register for launcher snapshot logging if this is the primary ModelDelegate
diff --git a/quickstep/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictionsImpl.java b/quickstep/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictionsImpl.java
index b982688..8b71f01 100644
--- a/quickstep/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictionsImpl.java
+++ b/quickstep/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictionsImpl.java
@@ -22,7 +22,6 @@
 import com.android.launcher3.appprediction.AppsDividerView;
 import com.android.launcher3.appprediction.PredictionRowView;
 import com.android.launcher3.model.BgDataModel;
-import com.android.launcher3.util.OnboardingPrefs;
 import com.android.launcher3.views.ActivityContext;
 
 /**
@@ -30,22 +29,21 @@
  */
 @SuppressWarnings("unused")
 public final class SecondaryDisplayPredictionsImpl extends SecondaryDisplayPredictions {
+
     private final ActivityContext mActivityContext;
+    private final Context mContext;
 
     public SecondaryDisplayPredictionsImpl(Context context) {
+        mContext = context;
         mActivityContext = ActivityContext.lookupContext(context);
     }
 
     @Override
     void updateAppDivider() {
-        OnboardingPrefs<?> onboardingPrefs = mActivityContext.getOnboardingPrefs();
-        if (onboardingPrefs != null) {
-            mActivityContext.getAppsView().getFloatingHeaderView()
-                    .findFixedRowByType(AppsDividerView.class)
-                    .setShowAllAppsLabel(
-                            !onboardingPrefs.hasReachedMaxCount(ALL_APPS_VISITED_COUNT));
-            onboardingPrefs.incrementEventCount(ALL_APPS_VISITED_COUNT);
-        }
+        mActivityContext.getAppsView().getFloatingHeaderView()
+                .findFixedRowByType(AppsDividerView.class)
+                .setShowAllAppsLabel(!ALL_APPS_VISITED_COUNT.hasReachedMax(mContext));
+        ALL_APPS_VISITED_COUNT.increment(mContext);
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
index 42e6809..00a282a 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
@@ -15,8 +15,10 @@
  */
 package com.android.launcher3.statehandlers;
 
+import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
+import android.os.Debug;
 import android.os.SystemProperties;
 import android.util.Log;
 import android.view.View;
@@ -46,6 +48,7 @@
 
     private boolean mFreeformTasksVisible;
     private boolean mInOverviewState;
+    private boolean mBackgroundStateEnabled;
     private boolean mGestureInProgress;
 
     @Nullable
@@ -113,7 +116,11 @@
      * Whether freeform windows are visible in desktop mode.
      */
     public boolean areFreeformTasksVisible() {
-        return mFreeformTasksVisible;
+        if (DEBUG) {
+            Log.d(TAG, "areFreeformTasksVisible: freeformVisible=" + mFreeformTasksVisible
+                    + " overview=" + mInOverviewState);
+        }
+        return mFreeformTasksVisible && !mInOverviewState;
     }
 
     /**
@@ -121,7 +128,8 @@
      */
     public void setFreeformTasksVisible(boolean freeformTasksVisible) {
         if (DEBUG) {
-            Log.d(TAG, "setFreeformTasksVisible: visible=" + freeformTasksVisible);
+            Log.d(TAG, "setFreeformTasksVisible: visible=" + freeformTasksVisible
+                    + " currentValue=" + mFreeformTasksVisible);
         }
         if (!isDesktopModeSupported()) {
             return;
@@ -146,11 +154,21 @@
     }
 
     /**
-     * Sets whether the overview is visible and updates launcher visibility based on that.
+     * Process launcher state change and update launcher view visibility based on desktop state
      */
-    public void setOverviewStateEnabled(boolean overviewStateEnabled) {
+    public void onLauncherStateChanged(LauncherState state) {
         if (DEBUG) {
-            Log.d(TAG, "setOverviewStateEnabled: enabled=" + overviewStateEnabled);
+            Log.d(TAG, "onLauncherStateChanged: newState=" + state);
+        }
+        setBackgroundStateEnabled(state == BACKGROUND_APP);
+        // Desktop visibility tracks overview and background state separately
+        setOverviewStateEnabled(state != BACKGROUND_APP && state.overviewUi);
+    }
+
+    private void setOverviewStateEnabled(boolean overviewStateEnabled) {
+        if (DEBUG) {
+            Log.d(TAG, "setOverviewStateEnabled: enabled=" + overviewStateEnabled
+                    + " currentValue=" + mInOverviewState);
         }
         if (!isDesktopModeSupported()) {
             return;
@@ -160,7 +178,7 @@
             if (mInOverviewState) {
                 setLauncherViewsVisibility(View.VISIBLE);
                 markLauncherResumed();
-            } else if (mFreeformTasksVisible && !mGestureInProgress) {
+            } else if (areFreeformTasksVisible() && !mGestureInProgress) {
                 // Switching out of overview state and gesture finished.
                 // If freeform tasks are still visible, hide launcher again.
                 setLauncherViewsVisibility(View.INVISIBLE);
@@ -169,6 +187,27 @@
         }
     }
 
+    private void setBackgroundStateEnabled(boolean backgroundStateEnabled) {
+        if (DEBUG) {
+            Log.d(TAG, "setBackgroundStateEnabled: enabled=" + backgroundStateEnabled
+                    + " currentValue=" + mBackgroundStateEnabled);
+        }
+        if (!isDesktopModeSupported()) {
+            return;
+        }
+        if (backgroundStateEnabled != mBackgroundStateEnabled) {
+            mBackgroundStateEnabled = backgroundStateEnabled;
+            if (mBackgroundStateEnabled) {
+                setLauncherViewsVisibility(View.VISIBLE);
+                markLauncherResumed();
+            } else if (areFreeformTasksVisible() && !mGestureInProgress) {
+                // Switching out of background state. If freeform tasks are visible, pause launcher.
+                setLauncherViewsVisibility(View.INVISIBLE);
+                markLauncherPaused();
+            }
+        }
+    }
+
     /**
      * Whether recents gesture is currently in progress.
      */
@@ -183,6 +222,9 @@
         if (!isDesktopModeSupported()) {
             return;
         }
+        if (DEBUG) {
+            Log.d(TAG, "setRecentsGestureStart");
+        }
         setRecentsGestureInProgress(true);
     }
 
@@ -194,6 +236,9 @@
         if (!isDesktopModeSupported()) {
             return;
         }
+        if (DEBUG) {
+            Log.d(TAG, "setRecentsGestureEnd: endTarget=" + endTarget);
+        }
         setRecentsGestureInProgress(false);
 
         if (endTarget == null) {
@@ -203,9 +248,6 @@
     }
 
     private void setRecentsGestureInProgress(boolean gestureInProgress) {
-        if (DEBUG) {
-            Log.d(TAG, "setGestureInProgress: inProgress=" + gestureInProgress);
-        }
         if (gestureInProgress != mGestureInProgress) {
             mGestureInProgress = gestureInProgress;
         }
@@ -222,7 +264,8 @@
 
     private void setLauncherViewsVisibility(int visibility) {
         if (DEBUG) {
-            Log.d(TAG, "setLauncherViewsVisibility: visibility=" + visibility);
+            Log.d(TAG, "setLauncherViewsVisibility: visibility=" + visibility + " "
+                    + Debug.getCaller());
         }
         View workspaceView = mLauncher.getWorkspace();
         if (workspaceView != null) {
@@ -236,7 +279,7 @@
 
     private void markLauncherPaused() {
         if (DEBUG) {
-            Log.d(TAG, "markLauncherPaused");
+            Log.d(TAG, "markLauncherPaused " + Debug.getCaller());
         }
         StatefulActivity<LauncherState> activity =
                 QuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity();
@@ -247,7 +290,7 @@
 
     private void markLauncherResumed() {
         if (DEBUG) {
-            Log.d(TAG, "markLauncherResumed");
+            Log.d(TAG, "markLauncherResumed " + Debug.getCaller());
         }
         StatefulActivity<LauncherState> activity =
                 QuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity();
diff --git a/quickstep/src/com/android/launcher3/taskbar/BaseTaskbarContext.java b/quickstep/src/com/android/launcher3/taskbar/BaseTaskbarContext.java
index 331184a..c201236 100644
--- a/quickstep/src/com/android/launcher3/taskbar/BaseTaskbarContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/BaseTaskbarContext.java
@@ -20,8 +20,6 @@
 import android.view.LayoutInflater;
 
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
-import com.android.launcher3.LauncherPrefs;
-import com.android.launcher3.util.OnboardingPrefs;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.ActivityContext;
 
@@ -34,12 +32,10 @@
 
     protected final LayoutInflater mLayoutInflater;
     private final List<OnDeviceProfileChangeListener> mDPChangeListeners = new ArrayList<>();
-    private final OnboardingPrefs<BaseTaskbarContext> mOnboardingPrefs;
 
     public BaseTaskbarContext(Context windowContext) {
         super(windowContext, Themes.getActivityThemeRes(windowContext));
         mLayoutInflater = LayoutInflater.from(this).cloneInContext(this);
-        mOnboardingPrefs = new OnboardingPrefs<>(this, LauncherPrefs.getPrefs(this));
     }
 
     @Override
@@ -52,11 +48,6 @@
         return mDPChangeListeners;
     }
 
-    @Override
-    public OnboardingPrefs<BaseTaskbarContext> getOnboardingPrefs() {
-        return mOnboardingPrefs;
-    }
-
     /** Callback invoked when a drag is initiated within this context. */
     public abstract void onDragStart();
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index e6dfe0f..a321734 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -288,8 +288,7 @@
 
         // Persistent features EDU tooltip.
         if (!DisplayController.isTransientTaskbar(mLauncher)) {
-            return !mLauncher.getOnboardingPrefs().hasReachedMaxCount(
-                    OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP);
+            return !OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP.hasReachedMax(mLauncher);
         }
 
         // Transient swipe EDU tooltip.
@@ -337,7 +336,7 @@
     }
 
     public boolean isBubbleBarEnabled() {
-        return BubbleBarController.BUBBLE_BAR_ENABLED;
+        return BubbleBarController.isBubbleBarEnabled();
     }
 
     /** Whether the bubble bar has any bubbles. */
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 4b16019..be4426d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -471,7 +471,7 @@
     /**
      * @return {@code true} if A11y is showing in 3 button nav taskbar
      */
-    private boolean isContextualButtonShowing() {
+    private boolean isA11yButtonPersistent() {
         return mContext.isThreeButtonNav() && (mState & FLAG_A11Y_VISIBLE) != 0;
     }
 
@@ -742,7 +742,7 @@
                             mA11yButton, res, isInKidsMode, isInSetup, isThreeButtonNav,
                             TaskbarManager.isPhoneMode(dp),
                             mWindowManagerProxy.getRotation(mContext));
-            navButtonLayoutter.layoutButtons(dp, isContextualButtonShowing());
+            navButtonLayoutter.layoutButtons(dp, isA11yButtonPersistent());
             updateNavButtonColor();
             return;
         }
@@ -838,7 +838,7 @@
             int contextualWidth = mEndContextualContainer.getWidth();
             // If contextual buttons are showing, we check if the end margin is enough for the
             // contextual button to be showing - if not, move the nav buttons over a smidge
-            if (isContextualButtonShowing() && navMarginEnd < contextualWidth) {
+            if (isA11yButtonPersistent() && navMarginEnd < contextualWidth) {
                 // Additional spacing, eat up half of space between last icon and nav button
                 navMarginEnd += res.getDimensionPixelSize(R.dimen.taskbar_hotseat_nav_spacing) / 2;
             }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 0aa02f2..d4faf47 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -27,6 +27,7 @@
 import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
 import static com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_OVERLAY_PROXY;
 import static com.android.launcher3.Utilities.isRunningInTestHarness;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_PINNING;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NO_RECREATION;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_OPEN;
 import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_DRAGGING;
@@ -218,7 +219,8 @@
 
         // If Bubble bar is present, TaskbarControllers depends on it so build it first.
         Optional<BubbleControllers> bubbleControllersOptional = Optional.empty();
-        if (BubbleBarController.BUBBLE_BAR_ENABLED && bubbleBarView != null) {
+        BubbleBarController.onTaskbarRecreated();
+        if (BubbleBarController.isBubbleBarEnabled() && bubbleBarView != null) {
             bubbleControllersOptional = Optional.of(new BubbleControllers(
                     new BubbleBarController(this, bubbleBarView),
                     new BubbleBarViewController(this, bubbleBarView),
@@ -842,9 +844,17 @@
             return getSetupWindowHeight();
         }
 
-        if (DisplayController.isTransientTaskbar(this)) {
-            return mDeviceProfile.taskbarHeight
-                    + (2 * mDeviceProfile.taskbarBottomMargin)
+        boolean shouldTreatAsTransient = DisplayController.isTransientTaskbar(this)
+                || (ENABLE_TASKBAR_PINNING.get() && !isThreeButtonNav());
+
+        // Return transient taskbar window height when pinning feature is enabled, so taskbar view
+        // does not get cut off during pinning animation.
+        if (shouldTreatAsTransient) {
+            DeviceProfile transientTaskbarDp = mDeviceProfile.toBuilder(this)
+                    .setIsTransientTaskbar(true).build();
+
+            return transientTaskbarDp.taskbarHeight
+                    + (2 * transientTaskbarDp.taskbarBottomMargin)
                     + resources.getDimensionPixelSize(R.dimen.transient_taskbar_shadow_blur);
         }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index 4ad5c88..6ddf9e9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -21,6 +21,8 @@
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
 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 android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -64,6 +66,7 @@
 import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.graphics.DragPreviewProvider;
+import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -626,7 +629,9 @@
 
         if (tag instanceof ItemInfo) {
             ItemInfo item = (ItemInfo) tag;
-            if (item.container == CONTAINER_ALL_APPS || item.container == CONTAINER_PREDICTION) {
+            if (item.container == CONTAINER_ALL_APPS
+                    || item.container == CONTAINER_PREDICTION
+                    || isInSearchResultContainer(item)) {
                 if (mDisallowGlobalDrag) {
                     // We're dragging in taskbarAllApps, we don't have folders or shortcuts
                     return iconView;
@@ -648,6 +653,13 @@
         return iconView;
     }
 
+    private static boolean isInSearchResultContainer(ItemInfo item) {
+        ContainerInfo containerInfo = item.getContainerInfo();
+        return containerInfo.getContainerCase() == EXTENDED_CONTAINERS
+                && containerInfo.getExtendedContainers().getContainerCase()
+                        == DEVICE_SEARCH_RESULT_CONTAINER;
+    }
+
     private void setupReturnDragAnimator(float fromX, float fromY, View originalView,
             TaskbarReturnPropertiesListener animListener) {
         // Finish any pending return animation before starting a new return
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
index de4175d..0ac2019 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
@@ -67,11 +67,10 @@
     @TaskbarEduTooltipStep
     var tooltipStep: Int
         get() {
-            return activityContext.onboardingPrefs?.getCount(TASKBAR_EDU_TOOLTIP_STEP)
-                ?: TOOLTIP_STEP_NONE
+            return TASKBAR_EDU_TOOLTIP_STEP.get(activityContext)
         }
         private set(step) {
-            activityContext.onboardingPrefs?.setEventCount(step, TASKBAR_EDU_TOOLTIP_STEP)
+            TASKBAR_EDU_TOOLTIP_STEP.set(step, activityContext)
         }
 
     private var tooltip: TaskbarEduTooltip? = null
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 88ae349..3bfeee8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -45,6 +45,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatedFloat;
 import com.android.launcher3.anim.AnimatorListeners;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
@@ -209,7 +210,10 @@
                         updateStateForFlag(FLAG_RESUMED, true);
                     }
                     applyState();
-                    boolean disallowLongClick = finalState == LauncherState.OVERVIEW_SPLIT_SELECT;
+                    boolean disallowLongClick =
+                            FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()
+                                    ? mLauncher.isSplitSelectionEnabled()
+                                    : finalState == LauncherState.OVERVIEW_SPLIT_SELECT;
                     com.android.launcher3.taskbar.Utilities.setOverviewDragState(
                             mControllers, finalState.disallowTaskbarGlobalDrag(),
                             disallowLongClick, finalState.allowTaskbarInitialSplitSelection());
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index ce901f2..6dfd243 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -48,6 +48,7 @@
 import android.provider.Settings;
 import android.util.Log;
 import android.view.Display;
+import android.view.MotionEvent;
 import android.view.WindowManager;
 import android.widget.FrameLayout;
 
@@ -211,7 +212,18 @@
         mContext = service.createWindowContext(display, TYPE_NAVIGATION_BAR_PANEL, null);
         if (ENABLE_TASKBAR_NO_RECREATION.get()) {
             mWindowManager = mContext.getSystemService(WindowManager.class);
-            mTaskbarRootLayout = new FrameLayout(mContext);
+            mTaskbarRootLayout = new FrameLayout(mContext) {
+                @Override
+                public boolean dispatchTouchEvent(MotionEvent ev) {
+                    // The motion events can be outside the view bounds of task bar, and hence
+                    // manually dispatching them to the drag layer here.
+                    if (mTaskbarActivityContext != null
+                            && mTaskbarActivityContext.getDragLayer().isAttachedToWindow()) {
+                        return mTaskbarActivityContext.getDragLayer().dispatchTouchEvent(ev);
+                    }
+                    return super.dispatchTouchEvent(ev);
+                }
+            };
         }
         mNavButtonController = new TaskbarNavButtonController(service,
                 SystemUiProxy.INSTANCE.get(mContext), new Handler(),
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java
index a0ce976..712374d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java
@@ -17,7 +17,7 @@
 
 import static android.view.View.VISIBLE;
 
-import static com.android.launcher3.taskbar.bubbles.BubbleBarController.BUBBLE_BAR_ENABLED;
+import static com.android.launcher3.taskbar.bubbles.BubbleBarController.isBubbleBarEnabled;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED;
 import static com.android.wm.shell.common.bubbles.BubbleConstants.BUBBLE_EXPANDED_SCRIM_ALPHA;
@@ -83,7 +83,7 @@
      * Updates the scrim state based on the flags.
      */
     public void updateStateForSysuiFlags(int stateFlags, boolean skipAnim) {
-        if (BUBBLE_BAR_ENABLED && DisplayController.isTransientTaskbar(mActivity)) {
+        if (isBubbleBarEnabled() && DisplayController.isTransientTaskbar(mActivity)) {
             // These scrims aren't used if bubble bar & transient taskbar are active.
             return;
         }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index f2b60b9..e67a6d5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -278,8 +278,14 @@
         mSystemUiProxy = SystemUiProxy.INSTANCE.get(activity);
         mAccessibilityManager = mActivity.getSystemService(AccessibilityManager.class);
 
-        mUnstashedHeight = mActivity.getDeviceProfile().taskbarHeight;
-        mStashedHeight = mActivity.getDeviceProfile().stashedTaskbarHeight;
+        if (isPhoneMode()) {
+            mUnstashedHeight = mActivity.getResources().getDimensionPixelSize(R.dimen.taskbar_size);
+            mStashedHeight = mActivity.getResources().getDimensionPixelSize(
+                    R.dimen.taskbar_stashed_size);
+        } else {
+            mUnstashedHeight = mActivity.getDeviceProfile().taskbarHeight;
+            mStashedHeight = mActivity.getDeviceProfile().stashedTaskbarHeight;
+        }
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index b405320..8a8c3bc 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -122,7 +122,9 @@
     private AnimatorPlaybackController mIconAlignControllerLazy = null;
     private Runnable mOnControllerPreCreateCallback = NO_OP;
 
+    // Stored here as signals to determine if the mIconAlignController needs to be recreated.
     private boolean mIsHotseatIconOnTopWhenAligned;
+    private boolean mIsStashed;
 
     private final DeviceProfile.OnDeviceProfileChangeListener mDeviceProfileChangeListener =
             dp -> commitRunningAppsToUI();
@@ -435,10 +437,13 @@
     public void setLauncherIconAlignment(float alignmentRatio, DeviceProfile launcherDp) {
         boolean isHotseatIconOnTopWhenAligned =
                 mControllers.uiController.isHotseatIconOnTopWhenAligned();
-        // When mIsHotseatIconOnTopWhenAligned changes, animation needs to be re-created.
+        boolean isStashed = mControllers.taskbarStashController.isStashed();
+        // Re-create animation when mIsHotseatIconOnTopWhenAligned or mIsStashed changes.
         if (mIconAlignControllerLazy == null
-                || mIsHotseatIconOnTopWhenAligned != isHotseatIconOnTopWhenAligned) {
+                || mIsHotseatIconOnTopWhenAligned != isHotseatIconOnTopWhenAligned
+                || mIsStashed != isStashed) {
             mIsHotseatIconOnTopWhenAligned = isHotseatIconOnTopWhenAligned;
+            mIsStashed = isStashed;
             mIconAlignControllerLazy = createIconAlignmentController(launcherDp);
         }
         mIconAlignControllerLazy.setPlayFraction(alignmentRatio);
@@ -458,7 +463,7 @@
      */
     private AnimatorPlaybackController createIconAlignmentController(DeviceProfile launcherDp) {
         PendingAnimation setter = new PendingAnimation(100);
-        if (TaskbarManager.isPhoneButtonNavMode(mActivity)) {
+        if (TaskbarManager.isPhoneMode(launcherDp)) {
             // No animation for icons in small-screen
             return setter.createPlaybackController();
         }
@@ -505,7 +510,7 @@
                     || (isTaskbarDividerView && FeatureFlags.ENABLE_TASKBAR_PINNING.get())) {
                 if (!isToHome
                         && mIsHotseatIconOnTopWhenAligned
-                        && mControllers.taskbarStashController.isStashed()) {
+                        && mIsStashed) {
                     // Prevent All Apps icon from appearing when going from hotseat to nav handle.
                     setter.setViewAlpha(child, 0, Interpolators.clampToProgress(LINEAR, 0f, 0f));
                 } else {
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
index 5182a32..07d86e4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
@@ -158,10 +158,6 @@
         if (mAppsView != null) {
             return;
         }
-        // mControllers and getSharedState should never be null here. Do not handle null-pointer
-        // to catch invalid states.
-        mControllers.getSharedState().allAppsVisible = true;
-
         mOverlayContext = mControllers.taskbarOverlayController.requestWindow();
 
         // Initialize search session for All Apps.
@@ -178,10 +174,7 @@
         // Ensures All Apps gets touch events in case it is not the top floating view. Floating
         // views above it may not be able to intercept the touch, so All Apps should try to.
         mOverlayContext.getDragLayer().addTouchController(mSlideInView);
-        mSlideInView.addOnCloseListener(() -> {
-            mControllers.getSharedState().allAppsVisible = false;
-            cleanUpOverlay();
-        });
+        mSlideInView.addOnCloseListener(this::cleanUpOverlay);
         TaskbarAllAppsViewController viewController = new TaskbarAllAppsViewController(
                 mOverlayContext,
                 mSlideInView,
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
index 6d740c0..b1c5151 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
@@ -18,17 +18,22 @@
 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_STASHED_IN_TASKBAR_ALL_APPS;
 import static com.android.launcher3.util.OnboardingPrefs.ALL_APPS_VISITED_COUNT;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.allapps.AllAppsTransitionListener;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.appprediction.AppsDividerView;
 import com.android.launcher3.taskbar.NavbarButtonsViewController;
 import com.android.launcher3.taskbar.TaskbarControllers;
+import com.android.launcher3.taskbar.TaskbarSharedState;
 import com.android.launcher3.taskbar.TaskbarStashController;
 import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
 import com.android.launcher3.taskbar.overlay.TaskbarOverlayController;
 import com.android.launcher3.util.DisplayController;
 
+import java.util.Optional;
+
 /**
  * Handles the {@link TaskbarAllAppsContainerView} behavior and synchronizes its transitions with
  * taskbar stashing.
@@ -41,6 +46,7 @@
     private final TaskbarStashController mTaskbarStashController;
     private final NavbarButtonsViewController mNavbarButtonsViewController;
     private final TaskbarOverlayController mOverlayController;
+    private final @Nullable TaskbarSharedState mTaskbarSharedState;
     private final boolean mShowKeyboard;
 
     TaskbarAllAppsViewController(
@@ -56,6 +62,7 @@
         mTaskbarStashController = taskbarControllers.taskbarStashController;
         mNavbarButtonsViewController = taskbarControllers.navbarButtonsViewController;
         mOverlayController = taskbarControllers.taskbarOverlayController;
+        mTaskbarSharedState = taskbarControllers.getSharedState();
         mShowKeyboard = showKeyboard;
 
         mSlideInView.init(new TaskbarAllAppsCallbacks(searchSessionController));
@@ -76,9 +83,8 @@
     private void setUpAppDivider() {
         mAppsView.getFloatingHeaderView()
                 .findFixedRowByType(AppsDividerView.class)
-                .setShowAllAppsLabel(!mContext.getOnboardingPrefs().hasReachedMaxCount(
-                        ALL_APPS_VISITED_COUNT));
-        mContext.getOnboardingPrefs().incrementEventCount(ALL_APPS_VISITED_COUNT);
+                .setShowAllAppsLabel(!ALL_APPS_VISITED_COUNT.hasReachedMax(mContext));
+        ALL_APPS_VISITED_COUNT.increment(mContext);
     }
 
     private void setUpTaskbarStashing() {
@@ -87,8 +93,10 @@
             mTaskbarStashController.applyState();
         }
 
+        Optional.ofNullable(mTaskbarSharedState).ifPresent(s -> s.allAppsVisible = true);
         mNavbarButtonsViewController.setSlideInViewVisible(true);
         mSlideInView.setOnCloseBeginListener(() -> {
+            Optional.ofNullable(mTaskbarSharedState).ifPresent(s -> s.allAppsVisible = false);
             mNavbarButtonsViewController.setSlideInViewVisible(false);
             AbstractFloatingView.closeOpenContainer(
                     mContext, AbstractFloatingView.TYPE_ACTION_POPUP);
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index bd11efd..3fb7247 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -85,17 +85,31 @@
  * information to render each of the bubbles & dispatches changes to
  * {@link BubbleBarViewController} which will then update {@link BubbleBarView} as needed.
  *
- * For details around the behavior of the bubble bar, see {@link BubbleBarView}.
+ * <p>For details around the behavior of the bubble bar, see {@link BubbleBarView}.
  */
 public class BubbleBarController extends IBubblesListener.Stub {
 
     private static final String TAG = BubbleBarController.class.getSimpleName();
     private static final boolean DEBUG = false;
 
-    // Whether bubbles are showing in the bubble bar from launcher
-    public static final boolean BUBBLE_BAR_ENABLED =
+    /**
+     * Determines whether bubbles can be shown in the bubble bar. This value updates when the
+     * taskbar is recreated.
+     *
+     * @see #onTaskbarRecreated()
+     */
+    private static boolean sBubbleBarEnabled =
             SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false);
 
+    /** Whether showing bubbles in the launcher bubble bar is enabled. */
+    public static boolean isBubbleBarEnabled() {
+        return sBubbleBarEnabled;
+    }
+
+    /** Re-reads the value of the flag from SystemProperties when taskbar is recreated. */
+    public static void onTaskbarRecreated() {
+        sBubbleBarEnabled = SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false);
+    }
     private static final int MASK_HIDE_BUBBLE_BAR = SYSUI_STATE_BOUNCER_SHOWING
             | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
             | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED
@@ -167,7 +181,7 @@
 
         mSystemUiProxy = SystemUiProxy.INSTANCE.get(context);
 
-        if (BUBBLE_BAR_ENABLED) {
+        if (sBubbleBarEnabled) {
             mSystemUiProxy.setBubblesListener(this);
         }
         mMainExecutor = MAIN_EXECUTOR;
@@ -191,9 +205,9 @@
 
         bubbleControllers.runAfterInit(() -> {
             mBubbleBarViewController.setHiddenForBubbles(
-                    !BUBBLE_BAR_ENABLED || mBubbles.isEmpty());
+                    !sBubbleBarEnabled || mBubbles.isEmpty());
             mBubbleStashedHandleViewController.setHiddenForBubbles(
-                    !BUBBLE_BAR_ENABLED || mBubbles.isEmpty());
+                    !sBubbleBarEnabled || mBubbles.isEmpty());
             mBubbleBarViewController.setUpdateSelectedBubbleAfterCollapse(
                     key -> setSelectedBubble(mBubbles.get(key)));
         });
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
index 9758d44..7529508 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
@@ -46,7 +46,7 @@
         protected val startContextualContainer: ViewGroup,
         protected val imeSwitcher: ImageView?,
         protected val rotationButton: RotationButton?,
-        protected val a11yButton: ImageView
+        protected val a11yButton: ImageView?
 ) : NavButtonLayoutter {
     protected val homeButton: ImageView? = navButtonContainer.findViewById(R.id.home)
     protected val recentsButton: ImageView? = navButtonContainer.findViewById(R.id.recent_apps)
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
index f254ee8..cb37cc7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
@@ -35,7 +35,7 @@
         startContextualContainer: ViewGroup,
         imeSwitcher: ImageView?,
         rotationButton: RotationButton?,
-        a11yButton: ImageView
+        a11yButton: ImageView?
 ) :
     AbstractNavButtonLayoutter(
             resources,
@@ -47,7 +47,7 @@
             a11yButton
     ) {
 
-    override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
+    override fun layoutButtons(dp: DeviceProfile, isA11yButtonPersistent: Boolean) {
         val iconSize: Int = resources.getDimensionPixelSize(DIMEN_TASKBAR_ICON_SIZE_KIDS)
         val buttonWidth: Int = resources.getDimensionPixelSize(DIMEN_TASKBAR_NAV_BUTTONS_WIDTH_KIDS)
         val buttonHeight: Int =
@@ -114,7 +114,9 @@
             startContextualContainer.addView(imeSwitcher)
             imeSwitcher.layoutParams = getParamsToCenterView()
         }
-        endContextualContainer.addView(a11yButton)
+        if (a11yButton != null) {
+            endContextualContainer.addView(a11yButton)
+        }
         if (rotationButton != null) {
             endContextualContainer.addView(rotationButton.currentView)
             rotationButton.currentView.layoutParams = getParamsToCenterView()
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
index 7db1a37..6b05e9a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
@@ -58,7 +58,7 @@
                 navButtonsView: FrameLayout,
                 imeSwitcher: ImageView?,
                 rotationButton: RotationButton?,
-                a11yButton: ImageView,
+                a11yButton: ImageView?,
                 resources: Resources,
                 isKidsMode: Boolean,
                 isInSetup: Boolean,
@@ -162,6 +162,6 @@
 
     /** Lays out and provides access to the home, recents, and back buttons for various mischief */
     interface NavButtonLayoutter {
-        fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean)
+        fun layoutButtons(dp: DeviceProfile, isA11yButtonPersistent: Boolean)
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt
index c1dae40..5a7bc49 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt
@@ -31,7 +31,7 @@
         startContextualContainer: ViewGroup,
         imeSwitcher: ImageView?,
         rotationButton: RotationButton?,
-        a11yButton: ImageView
+        a11yButton: ImageView?
 ) :
         AbstractNavButtonLayoutter(
                 resources,
@@ -43,7 +43,7 @@
                 a11yButton
         ) {
 
-    override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
+    override fun layoutButtons(dp: DeviceProfile, isA11yButtonPersistent: Boolean) {
         endContextualContainer.removeAllViews()
         startContextualContainer.removeAllViews()
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
index 21bbca5..9903efa 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
@@ -36,7 +36,7 @@
         startContextualContainer: ViewGroup,
         imeSwitcher: ImageView?,
         rotationButton: RotationButton?,
-        a11yButton: ImageView,
+        a11yButton: ImageView?,
 ) :
     AbstractNavButtonLayoutter(
             resources,
@@ -48,7 +48,7 @@
             a11yButton
     ) {
 
-    override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
+    override fun layoutButtons(dp: DeviceProfile, 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(dp, resources,
@@ -90,6 +90,17 @@
             }
         }
 
+        repositionContextualButtons()
+    }
+
+    open fun addThreeButtons() {
+        // Swap recents and back button
+        navButtonContainer.addView(recentsButton)
+        navButtonContainer.addView(homeButton)
+        navButtonContainer.addView(backButton)
+    }
+
+    open fun repositionContextualButtons() {
         endContextualContainer.removeAllViews()
         startContextualContainer.removeAllViews()
 
@@ -102,17 +113,12 @@
             startContextualContainer.addView(imeSwitcher)
             imeSwitcher.layoutParams = getParamsToCenterView()
         }
-        startContextualContainer.addView(a11yButton)
+        if (a11yButton != null) {
+            startContextualContainer.addView(a11yButton)
+        }
         if (rotationButton != null) {
             startContextualContainer.addView(rotationButton.currentView)
             rotationButton.currentView.layoutParams = getParamsToCenterView()
         }
     }
-
-    open fun addThreeButtons() {
-        // Swap recents and back button
-        navButtonContainer.addView(recentsButton)
-        navButtonContainer.addView(homeButton)
-        navButtonContainer.addView(backButton)
-    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
index ad03e5b..8745fc7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
@@ -35,7 +35,7 @@
         startContextualContainer: ViewGroup,
         imeSwitcher: ImageView?,
         rotationButton: RotationButton?,
-        a11yButton: ImageView,
+        a11yButton: ImageView?,
 ) :
     AbstractNavButtonLayoutter(
             resources,
@@ -47,7 +47,7 @@
             a11yButton
     ) {
 
-    override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
+    override fun layoutButtons(dp: DeviceProfile, isA11yButtonPersistent: Boolean) {
         // TODO(b/230395757): Polish pending, this is just to make it usable
         val taskbarDimensions =
             DimensionUtils.getTaskbarPhoneDimensions(dp, resources,
@@ -111,7 +111,9 @@
             endContextualContainer.addView(imeSwitcher)
             imeSwitcher.layoutParams = getParamsToCenterView()
         }
-        endContextualContainer.addView(a11yButton)
+        if (a11yButton != null) {
+            endContextualContainer.addView(a11yButton)
+        }
         if (rotationButton != null) {
             endContextualContainer.addView(rotationButton.currentView)
             rotationButton.currentView.layoutParams = getParamsToCenterView()
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt
index cde39f3..cfe1276 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt
@@ -31,7 +31,7 @@
         startContextualContainer: ViewGroup,
         imeSwitcher: ImageView?,
         rotationButton: RotationButton?,
-        a11yButton: ImageView
+        a11yButton: ImageView?
 ) :
         PhoneLandscapeNavLayoutter(
                 resources,
@@ -48,7 +48,9 @@
         navButtonContainer.addView(backButton)
         navButtonContainer.addView(homeButton)
         navButtonContainer.addView(recentsButton)
+    }
 
+    override fun repositionContextualButtons() {
         endContextualContainer.removeAllViews()
         startContextualContainer.removeAllViews()
 
@@ -61,7 +63,9 @@
             endContextualContainer.addView(imeSwitcher)
             imeSwitcher.layoutParams = getParamsToCenterView()
         }
-        endContextualContainer.addView(a11yButton)
+        if (a11yButton != null) {
+            endContextualContainer.addView(a11yButton)
+        }
         if (rotationButton != null) {
             endContextualContainer.addView(rotationButton.currentView)
             rotationButton.currentView.layoutParams = getParamsToCenterView()
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
index db245b8..015fac4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
@@ -32,7 +32,7 @@
         startContextualContainer: ViewGroup,
         imeSwitcher: ImageView?,
         rotationButton: RotationButton?,
-        a11yButton: ImageView
+        a11yButton: ImageView?
 ) :
     AbstractNavButtonLayoutter(
             resources,
@@ -44,7 +44,7 @@
             a11yButton
     ) {
 
-    override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
+    override fun layoutButtons(dp: DeviceProfile, isA11yButtonPersistent: Boolean) {
         // Since setup wizard only has back button enabled, it looks strange to be
         // end-aligned, so start-align instead.
         val navButtonsLayoutParams = navButtonContainer.layoutParams as FrameLayout.LayoutParams
@@ -72,7 +72,9 @@
             startContextualContainer.addView(imeSwitcher)
             imeSwitcher.layoutParams = getParamsToCenterView()
         }
-        endContextualContainer.addView(a11yButton)
+        if (a11yButton != null) {
+            endContextualContainer.addView(a11yButton)
+        }
         if (rotationButton != null) {
             endContextualContainer.addView(rotationButton.currentView)
             rotationButton.currentView.layoutParams = getParamsToCenterView()
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
index 56e55bb..ccd5c72 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
@@ -26,7 +26,9 @@
 import com.android.launcher3.R
 import com.android.systemui.shared.rotation.RotationButton
 
-/** Layoutter for showing 3 button navigation on large screen */
+/**
+ * Layoutter for rendering task bar in large screen, both in 3-button and gesture nav mode.
+ */
 class TaskbarNavLayoutter(
         resources: Resources,
         navBarContainer: LinearLayout,
@@ -34,7 +36,7 @@
         startContextualContainer: ViewGroup,
         imeSwitcher: ImageView?,
         rotationButton: RotationButton?,
-        a11yButton: ImageView
+        a11yButton: ImageView?
 ) :
     AbstractNavButtonLayoutter(
             resources,
@@ -46,13 +48,13 @@
             a11yButton
     ) {
 
-    override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
+    override fun layoutButtons(dp: DeviceProfile, isA11yButtonPersistent: Boolean) {
         // Add spacing after the end of the last nav button
         var navMarginEnd = resources.getDimension(dp.inv.inlineNavButtonsEndSpacing).toInt()
         val contextualWidth = endContextualContainer.width
         // If contextual buttons are showing, we check if the end margin is enough for the
         // contextual button to be showing - if not, move the nav buttons over a smidge
-        if (isContextualButtonShowing && navMarginEnd < contextualWidth) {
+        if (isA11yButtonPersistent && navMarginEnd < contextualWidth) {
             // Additional spacing, eat up half of space between last icon and nav button
             navMarginEnd += resources.getDimensionPixelSize(R.dimen.taskbar_hotseat_nav_spacing) / 2
         }
@@ -89,24 +91,28 @@
         endContextualContainer.removeAllViews()
         startContextualContainer.removeAllViews()
 
-        val endContextualContainerParams = FrameLayout.LayoutParams(
-                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)
-        endContextualContainerParams.gravity = Gravity.END or Gravity.CENTER_VERTICAL
-        endContextualContainer.layoutParams = endContextualContainerParams
+        if (!dp.isGestureMode) {
+            val endContextualContainerParams = FrameLayout.LayoutParams(
+                    ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)
+            endContextualContainerParams.gravity = Gravity.END or Gravity.CENTER_VERTICAL
+            endContextualContainer.layoutParams = endContextualContainerParams
 
-        val startContextualContainerParams = FrameLayout.LayoutParams(
-                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)
-        startContextualContainerParams.gravity = Gravity.START or Gravity.CENTER_VERTICAL
-        startContextualContainer.layoutParams = startContextualContainerParams
+            val startContextualContainerParams = FrameLayout.LayoutParams(
+                    ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)
+            startContextualContainerParams.gravity = Gravity.START or Gravity.CENTER_VERTICAL
+            startContextualContainer.layoutParams = startContextualContainerParams
 
-        if (imeSwitcher != null) {
-            startContextualContainer.addView(imeSwitcher)
-            imeSwitcher.layoutParams = getParamsToCenterView()
-        }
-        endContextualContainer.addView(a11yButton)
-        if (rotationButton != null) {
-            endContextualContainer.addView(rotationButton.currentView)
-            rotationButton.currentView.layoutParams = getParamsToCenterView()
+            if (imeSwitcher != null) {
+                startContextualContainer.addView(imeSwitcher)
+                imeSwitcher.layoutParams = getParamsToCenterView()
+            }
+            if (a11yButton != null) {
+                endContextualContainer.addView(a11yButton)
+            }
+            if (rotationButton != null) {
+                endContextualContainer.addView(rotationButton.currentView)
+                rotationButton.currentView.layoutParams = getParamsToCenterView()
+            }
         }
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
index 475f465..2d32407 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -22,11 +22,17 @@
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
 import android.content.pm.ShortcutInfo;
+import android.graphics.drawable.ColorDrawable;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.ArrayMap;
 import android.window.RemoteTransition;
 
 import com.android.launcher3.Utilities;
+import com.android.launcher3.util.UserIconInfo;
 import com.android.quickstep.util.FadeOutRemoteTransition;
 
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -53,4 +59,41 @@
         options.setRemoteTransition(new RemoteTransition(new FadeOutRemoteTransition()));
         return options;
     }
+
+    /**
+     * Returns a map of all users on the device to their corresponding UI properties
+     */
+    public static Map<UserHandle, UserIconInfo> queryAllUsers(Context context) {
+        UserManager um = context.getSystemService(UserManager.class);
+        Map<UserHandle, UserIconInfo> users = new ArrayMap<>();
+        List<UserHandle> usersActual = um.getUserProfiles();
+        if (usersActual != null) {
+            for (UserHandle user : usersActual) {
+                long serial = um.getSerialNumberForUser(user);
+
+                // Simple check to check if the provided user is work profile
+                // TODO: Migrate to a better platform API
+                NoopDrawable d = new NoopDrawable();
+                boolean isWork = (d != context.getPackageManager().getUserBadgedIcon(d, user));
+                UserIconInfo info = new UserIconInfo(
+                        user,
+                        isWork ? UserIconInfo.TYPE_WORK : UserIconInfo.TYPE_MAIN,
+                        serial);
+                users.put(user, info);
+            }
+        }
+        return users;
+    }
+
+    private static class NoopDrawable extends ColorDrawable {
+        @Override
+        public int getIntrinsicHeight() {
+            return 1;
+        }
+
+        @Override
+        public int getIntrinsicWidth() {
+            return 1;
+        }
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 1b22c84..7d88f05 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -61,7 +61,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
-import android.content.SharedPreferences;
 import android.content.res.Configuration;
 import android.graphics.Color;
 import android.graphics.Rect;
@@ -186,6 +185,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.stream.Stream;
@@ -218,8 +218,6 @@
     private SplitWithKeyboardShortcutController mSplitWithKeyboardShortcutController;
     private SplitToWorkspaceController mSplitToWorkspaceController;
 
-    private AsyncClockEventDelegate mAsyncClockEventDelegate;
-
     /**
      * If Launcher restarted while in the middle of an Overview split select, it needs this data to
      * recover. In all other cases this will remain null.
@@ -345,11 +343,6 @@
     }
 
     @Override
-    protected QuickstepOnboardingPrefs createOnboardingPrefs(SharedPreferences sharedPrefs) {
-        return new QuickstepOnboardingPrefs(this, sharedPrefs);
-    }
-
-    @Override
     public void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
         onStateOrResumeChanging(false /* inTransition */);
@@ -497,10 +490,6 @@
             mSplitSelectStateController.onDestroy();
         }
 
-        if (mAsyncClockEventDelegate != null) {
-            mAsyncClockEventDelegate.onDestroy();
-        }
-
         super.onDestroy();
         mHotseatPredictionController.destroy();
         mSplitWithKeyboardShortcutController.onDestroy();
@@ -624,6 +613,7 @@
             mViewCapture = SettingsAwareViewCapture.getInstance(this).startCapture(getWindow());
         }
         getWindow().addPrivateFlags(PRIVATE_FLAG_OPTIMIZE_MEASURE);
+        QuickstepOnboardingPrefs.setup(this);
         View.setTraceLayoutSteps(TRACE_LAYOUTS);
         View.setTracedRequestLayoutClassClass(TRACE_RELAYOUT_CLASS);
     }
@@ -675,6 +665,9 @@
         floatingTaskView.setAlpha(1);
         floatingTaskView.addStagingAnimation(anim, startingTaskRect, tempRect,
                 false /* fadeWithThumbnail */, true /* isStagedTask */);
+        floatingTaskView.setOnClickListener(view ->
+                mSplitSelectStateController.getSplitAnimationController().
+                        playAnimPlaceholderToFullscreen(this, view, Optional.empty()));
         mSplitSelectStateController.setFirstFloatingTaskView(floatingTaskView);
         anim.addListener(new AnimatorListenerAdapter() {
             @Override
@@ -691,7 +684,7 @@
     }
 
     @Override
-    protected boolean isSplitSelectionEnabled() {
+    public boolean isSplitSelectionEnabled() {
         return mSplitSelectStateController.isSplitSelectActive();
     }
 
@@ -714,7 +707,8 @@
 
         if (ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
             // If Launcher pauses before both split apps are selected, exit split screen.
-            if (!mSplitSelectStateController.isBothSplitAppsConfirmed()) {
+            if (!mSplitSelectStateController.isBothSplitAppsConfirmed() &&
+                    !mSplitSelectStateController.isLaunchingFirstAppFullscreen()) {
                 mSplitSelectStateController.getSplitAnimationController()
                         .playPlaceholderDismissAnim(this);
             }
@@ -988,6 +982,13 @@
                 .playPlaceholderDismissAnim(this);
     }
 
+    @Override
+    public void dismissSplitSelection() {
+        super.dismissSplitSelection();
+        mSplitSelectStateController.getSplitAnimationController()
+                .playPlaceholderDismissAnim(this);
+    }
+
     public <T extends OverviewActionsView> T getActionsView() {
         return (T) mActionsView;
     }
@@ -1346,18 +1347,12 @@
         switch (name) {
             case "TextClock", "android.widget.TextClock" -> {
                 TextClock tc = new TextClock(context, attrs);
-                if (mAsyncClockEventDelegate == null) {
-                    mAsyncClockEventDelegate = new AsyncClockEventDelegate(this);
-                }
-                tc.setClockEventDelegate(mAsyncClockEventDelegate);
+                tc.setClockEventDelegate(AsyncClockEventDelegate.INSTANCE.get(this));
                 return tc;
             }
             case "AnalogClock", "android.widget.AnalogClock" -> {
                 AnalogClock ac = new AnalogClock(context, attrs);
-                if (mAsyncClockEventDelegate == null) {
-                    mAsyncClockEventDelegate = new AsyncClockEventDelegate(this);
-                }
-                ac.setClockEventDelegate(mAsyncClockEventDelegate);
+                ac.setClockEventDelegate(AsyncClockEventDelegate.INSTANCE.get(this));
                 return ac;
             }
         }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsFragment.java b/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsFragment.java
index a76eb43..5253e7a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsFragment.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsFragment.java
@@ -24,9 +24,17 @@
 import static android.view.View.VISIBLE;
 
 import static com.android.launcher3.LauncherPrefs.ALL_APPS_OVERVIEW_THRESHOLD;
+import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE;
+import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_TIMEOUT_MS;
 import static com.android.launcher3.settings.SettingsActivity.EXTRA_FRAGMENT_ARG_KEY;
 import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.PLUGIN_CHANGED;
 import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.pluginEnabledKey;
+import static com.android.launcher3.util.OnboardingPrefs.ALL_APPS_VISITED_COUNT;
+import static com.android.launcher3.util.OnboardingPrefs.HOME_BOUNCE_COUNT;
+import static com.android.launcher3.util.OnboardingPrefs.HOME_BOUNCE_SEEN;
+import static com.android.launcher3.util.OnboardingPrefs.HOTSEAT_DISCOVERY_TIP_COUNT;
+import static com.android.launcher3.util.OnboardingPrefs.HOTSEAT_LONGPRESS_TIP_SEEN;
+import static com.android.launcher3.util.OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP;
 
 import android.annotation.TargetApi;
 import android.content.ComponentName;
@@ -63,17 +71,16 @@
 import androidx.preference.SeekBarPreference;
 import androidx.preference.SwitchPreference;
 
+import com.android.launcher3.ConstantItem;
 import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.R;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.secondarydisplay.SecondaryDisplayLauncher;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
-import com.android.launcher3.util.OnboardingPrefs;
 import com.android.launcher3.util.SimpleBroadcastReceiver;
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -111,6 +118,9 @@
         if (FeatureFlags.ENABLE_ALL_APPS_FROM_OVERVIEW.get()) {
             addAllAppsFromOverviewCatergory();
         }
+        if (FeatureFlags.CUSTOM_LPNH_THRESHOLDS.get()) {
+            addCustomLpnhCatergory();
+        }
 
         if (getActivity() != null) {
             getActivity().setTitle("Developer Options");
@@ -378,51 +388,83 @@
     private void addOnboardingPrefsCatergory() {
         PreferenceCategory onboardingCategory = newCategory("Onboarding Flows");
         onboardingCategory.setSummary("Reset these if you want to see the education again.");
-        for (Map.Entry<String, String[]> titleAndKeys : OnboardingPrefs.ALL_PREF_KEYS.entrySet()) {
-            String title = titleAndKeys.getKey();
-            String[] keys = titleAndKeys.getValue();
-            Preference onboardingPref = new Preference(getContext());
-            onboardingPref.setTitle(title);
-            onboardingPref.setSummary("Tap to reset");
-            onboardingPref.setOnPreferenceClickListener(preference -> {
-                SharedPreferences.Editor sharedPrefsEdit = LauncherPrefs.getPrefs(getContext())
-                        .edit();
-                for (String key : keys) {
-                    sharedPrefsEdit.remove(key);
-                }
-                sharedPrefsEdit.apply();
-                Toast.makeText(getContext(), "Reset " + title, Toast.LENGTH_SHORT).show();
-                return true;
-            });
-            onboardingCategory.addPreference(onboardingPref);
-        }
+
+        onboardingCategory.addPreference(createOnboardPref("All Apps Bounce",
+                HOME_BOUNCE_SEEN.getSharedPrefKey(), HOME_BOUNCE_COUNT.getSharedPrefKey()));
+        onboardingCategory.addPreference(createOnboardPref("Hybrid Hotseat Education",
+                HOTSEAT_DISCOVERY_TIP_COUNT.getSharedPrefKey(),
+                HOTSEAT_LONGPRESS_TIP_SEEN.getSharedPrefKey()));
+        onboardingCategory.addPreference(createOnboardPref("Taskbar Education",
+                TASKBAR_EDU_TOOLTIP_STEP.getSharedPrefKey()));
+        onboardingCategory.addPreference(createOnboardPref("All Apps Visited Count",
+                ALL_APPS_VISITED_COUNT.getSharedPrefKey()));
+    }
+
+    private Preference createOnboardPref(String title, String... keys) {
+        Preference onboardingPref = new Preference(getContext());
+        onboardingPref.setTitle(title);
+        onboardingPref.setSummary("Tap to reset");
+        onboardingPref.setOnPreferenceClickListener(preference -> {
+            SharedPreferences.Editor sharedPrefsEdit = LauncherPrefs.getPrefs(getContext())
+                    .edit();
+            for (String key : keys) {
+                sharedPrefsEdit.remove(key);
+            }
+            sharedPrefsEdit.apply();
+            Toast.makeText(getContext(), "Reset " + title, Toast.LENGTH_SHORT).show();
+            return true;
+        });
+        return onboardingPref;
     }
 
     private void addAllAppsFromOverviewCatergory() {
         PreferenceCategory category = newCategory("All Apps from Overview Config");
+        category.addPreference(createSeekBarPreference("Threshold to open All Apps from Overview",
+                105, 500, 100, ALL_APPS_OVERVIEW_THRESHOLD));
+    }
 
-        SeekBarPreference thresholdPref = new SeekBarPreference(getContext());
-        thresholdPref.setTitle("Threshold to open All Apps from Overview");
-        thresholdPref.setSingleLineTitle(false);
+    private void addCustomLpnhCatergory() {
+        PreferenceCategory category = newCategory("Long Press Nav Handle Config");
+        category.addPreference(createSeekBarPreference("Slop multiplier (applied to edge slop, "
+                        + "which is generally already 50% higher than touch slop)",
+                25, 200, 100, LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE));
+        category.addPreference(createSeekBarPreference("Trigger milliseconds",
+                100, 500, 1, LONG_PRESS_NAV_HANDLE_TIMEOUT_MS));
+    }
 
-        // These values are 100x swipe up shift value (100 = where overview sits).
-        thresholdPref.setMax(500);
-        thresholdPref.setMin(105);
-        thresholdPref.setUpdatesContinuously(true);
-        thresholdPref.setIconSpaceReserved(false);
+    /**
+     * Create a preference with text and a seek bar. Should be added to a PreferenceCategory.
+     *
+     * @param title text to show for this seek bar
+     * @param min min value for the seek bar
+     * @param max max value for the seek bar
+     * @param scale how much to divide the value to convert int to float
+     * @param launcherPref used to store the current value
+     */
+    private SeekBarPreference createSeekBarPreference(String title, int min, int max, int scale,
+            ConstantItem<Integer> launcherPref) {
+        SeekBarPreference seekBarPref = new SeekBarPreference(getContext());
+        seekBarPref.setTitle(title);
+        seekBarPref.setSingleLineTitle(false);
+
+        seekBarPref.setMax(max);
+        seekBarPref.setMin(min);
+        seekBarPref.setUpdatesContinuously(true);
+        seekBarPref.setIconSpaceReserved(false);
         // Don't directly save to shared prefs, use LauncherPrefs instead.
-        thresholdPref.setPersistent(false);
-        thresholdPref.setOnPreferenceChangeListener((preference, newValue) -> {
-            LauncherPrefs.get(getContext()).put(ALL_APPS_OVERVIEW_THRESHOLD, newValue);
-            preference.setSummary(String.valueOf((int) newValue / 100f));
+        seekBarPref.setPersistent(false);
+        seekBarPref.setOnPreferenceChangeListener((preference, newValue) -> {
+            LauncherPrefs.get(getContext()).put(launcherPref, newValue);
+            preference.setSummary(String.valueOf(scale == 1 ? newValue
+                    : (int) newValue / (float) scale));
             return true;
         });
-        int value = LauncherPrefs.get(getContext()).get(ALL_APPS_OVERVIEW_THRESHOLD);
-        thresholdPref.setValue(value);
+        int value = LauncherPrefs.get(getContext()).get(launcherPref);
+        seekBarPref.setValue(value);
         // For some reason the initial value is not triggering the summary update, so call manually.
-        thresholdPref.getOnPreferenceChangeListener().onPreferenceChange(thresholdPref, value);
+        seekBarPref.getOnPreferenceChangeListener().onPreferenceChange(seekBarPref, value);
 
-        category.addPreference(thresholdPref);
+        return seekBarPref;
     }
 
     private String toName(String action) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java
index a68e753..6279f63 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java
@@ -116,8 +116,9 @@
         boolean defaultValue = DeviceConfig.getBoolean(NAMESPACE_LAUNCHER, key, defaultValueInCode);
         if (IS_DEBUG_DEVICE) {
             boolean currentValue = getSharedPreferences().getBoolean(key, defaultValue);
-            DebugFlag flag = new DeviceFlag(key, description, flagState, currentValue,
-                    defaultValueInCode);
+            DebugFlag flag = new DeviceFlag(key, description,
+                    (defaultValue == defaultValueInCode) ? flagState
+                            : defaultValue ? ENABLED : DISABLED, currentValue, defaultValueInCode);
             sDebugFlags.add(flag);
             return flag;
         } else {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
index 3e7d45e..1d55da3 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
@@ -21,9 +21,9 @@
 import android.graphics.Rect;
 
 import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.Flags;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.quickstep.views.RecentsView;
 
 /**
@@ -72,7 +72,7 @@
 
     @Override
     public boolean isTaskbarStashed(Launcher launcher) {
-        if (FeatureFlags.enableGridOnlyOverview()) {
+        if (Flags.enableGridOnlyOverview()) {
             return true;
         }
         return super.isTaskbarStashed(launcher);
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index e788cc4..4dfa81d 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -2084,18 +2084,9 @@
     }
 
     private void finishCurrentTransitionToRecents() {
-        if (mRecentsView != null
-                && mActivityInterface.getDesktopVisibilityController() != null
-                && mActivityInterface.getDesktopVisibilityController().areFreeformTasksVisible()) {
-            mRecentsView.switchToScreenshot(() -> {
-                mRecentsView.finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
-                        () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
-            });
-        } else {
-            mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
-            if (mRecentsAnimationController != null) {
-                mRecentsAnimationController.detachNavigationBarFromApp(true);
-            }
+        mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
+        if (mRecentsAnimationController != null) {
+            mRecentsAnimationController.detachNavigationBarFromApp(true);
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 8925bd6..25389c5 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -50,10 +50,10 @@
 import androidx.annotation.UiThread;
 
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Flags;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statehandlers.DesktopVisibilityController;
 import com.android.launcher3.statemanager.BaseState;
@@ -242,7 +242,7 @@
     public final void calculateTaskSize(Context context, DeviceProfile dp, Rect outRect,
             PagedOrientationHandler orientedState) {
         if (dp.isTablet) {
-            if (FeatureFlags.enableGridOnlyOverview()) {
+            if (Flags.enableGridOnlyOverview()) {
                 calculateGridTaskSize(context, dp, outRect, orientedState);
             } else {
                 calculateFocusTaskSize(context, dp, outRect);
@@ -339,7 +339,7 @@
             PagedOrientationHandler orientedState) {
         Resources res = context.getResources();
         Rect potentialTaskRect = new Rect();
-        if (FeatureFlags.enableGridOnlyOverview()) {
+        if (Flags.enableGridOnlyOverview()) {
             calculateGridSize(dp, potentialTaskRect);
         } else {
             calculateFocusTaskSize(context, dp, potentialTaskRect);
@@ -371,7 +371,7 @@
     public final void calculateModalTaskSize(Context context, DeviceProfile dp, Rect outRect,
             PagedOrientationHandler orientedState) {
         calculateTaskSize(context, dp, outRect, orientedState);
-        boolean isGridOnlyOverview = dp.isTablet && FeatureFlags.enableGridOnlyOverview();
+        boolean isGridOnlyOverview = dp.isTablet && Flags.enableGridOnlyOverview();
         int claimedSpaceBelow = isGridOnlyOverview
                 ? dp.overviewActionsTopMarginPx + dp.overviewActionsHeight + dp.stashedTaskbarHeight
                 : (dp.heightPx - outRect.bottom - dp.getInsets().bottom);
diff --git a/quickstep/src/com/android/quickstep/BootAwarePreloader.kt b/quickstep/src/com/android/quickstep/BootAwarePreloader.kt
index 35404a9..2fc4d04 100644
--- a/quickstep/src/com/android/quickstep/BootAwarePreloader.kt
+++ b/quickstep/src/com/android/quickstep/BootAwarePreloader.kt
@@ -19,7 +19,7 @@
 import android.util.Log
 import com.android.launcher3.LauncherAppState
 import com.android.launcher3.LauncherPrefs
-import com.android.launcher3.isBootAwareStartupDataEnabled
+import com.android.launcher3.moveStartupDataToDeviceProtectedStorageIsEnabled
 import com.android.launcher3.util.LockedUserState
 
 /**
@@ -33,7 +33,8 @@
     fun start(context: Context) {
         val lp = LauncherPrefs.get(context)
         when {
-            LockedUserState.get(context).isUserUnlocked || !isBootAwareStartupDataEnabled -> {
+            LockedUserState.get(context).isUserUnlocked ||
+                !moveStartupDataToDeviceProtectedStorageIsEnabled -> {
                 /* No-Op */
             }
             lp.isStartupDataMigrated -> {
diff --git a/quickstep/src/com/android/quickstep/InputConsumer.java b/quickstep/src/com/android/quickstep/InputConsumer.java
index 23def14..f898e2f 100644
--- a/quickstep/src/com/android/quickstep/InputConsumer.java
+++ b/quickstep/src/com/android/quickstep/InputConsumer.java
@@ -126,4 +126,14 @@
         }
         return name.toString();
     }
+
+    /**
+     * Returns an input consumer of the given class type, or null if none is found.
+     */
+    default <T extends InputConsumer> T getInputConsumerOfClass(Class<T> c) {
+        if (getClass().equals(c)) {
+            return c.cast(this);
+        }
+        return null;
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
index 1f8ddf0..406e9f4 100644
--- a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
+++ b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
@@ -185,6 +185,13 @@
         @Override
         public void onBackProgressed(BackMotionEvent backMotionEvent) {
             mHandler.post(() -> {
+                LauncherBackAnimationController controller = mControllerRef.get();
+                if (controller == null
+                        || controller.mLauncher == null
+                        || !controller.mLauncher.isStarted()) {
+                    // Skip animating back progress if Launcher isn't visible yet.
+                    return;
+                }
                 mProgressAnimator.onBackProgressed(backMotionEvent);
             });
         }
@@ -294,6 +301,10 @@
         SurfaceControl parent = viewRootImpl != null
                 ? viewRootImpl.getSurfaceControl()
                 : null;
+        if (parent == null || !parent.isValid()) {
+            // Parent surface is not ready at the moment. Retry later.
+            return;
+        }
         boolean isDarkTheme = Utilities.isDarkTheme(mLauncher);
         mScrimLayer = new SurfaceControl.Builder()
                 .setName("Back to launcher background scrim")
@@ -326,6 +337,10 @@
         if (!mBackInProgress || mBackTarget == null) {
             return;
         }
+        if (mScrimLayer == null) {
+            // Scrim hasn't been attached yet. Let's attach it.
+            addScrimLayer();
+        }
         float screenWidth = mStartRect.width();
         float screenHeight = mStartRect.height();
         float width = Utilities.mapRange(progress, 1, MIN_WINDOW_SCALE) * screenWidth;
@@ -446,6 +461,9 @@
             mScrimAlphaAnimator.cancel();
             mScrimAlphaAnimator = null;
         }
+        if (mScrimLayer != null) {
+            removeScrimLayer();
+        }
     }
 
     private void startTransitionAnimations(RectFSpringAnim springAnim, AnimatorSet anim) {
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 6ee2cfd..3bc77ff 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -16,6 +16,8 @@
 
 package com.android.quickstep;
 
+import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
+
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.quickstep.util.SplitScreenUtils.convertShellSplitBoundsToLauncher;
 import static com.android.quickstep.views.DesktopTaskView.DESKTOP_MODE_SUPPORTED;
@@ -269,6 +271,7 @@
 
         TaskLoadResult allTasks = new TaskLoadResult(requestId, loadKeysOnly, rawTasks.size());
 
+        int numVisibleTasks = 0;
         for (GroupedRecentTaskInfo rawTask : rawTasks) {
             if (DESKTOP_MODE_SUPPORTED && rawTask.getType() == TYPE_FREEFORM) {
                 GroupTask desktopTask = createDesktopTask(rawTask);
@@ -285,12 +288,27 @@
             task1.setLastSnapshotData(taskInfo1);
             Task task2 = null;
             if (taskInfo2 != null) {
+                // Is split task
                 Task.TaskKey task2Key = new Task.TaskKey(taskInfo2);
                 task2 = loadKeysOnly
                         ? new Task(task2Key)
                         : Task.from(task2Key, taskInfo2,
                                 tmpLockedUsers.get(task2Key.userId) /* isLocked */);
                 task2.setLastSnapshotData(taskInfo2);
+            } else {
+                // Is fullscreen task
+                if (numVisibleTasks > 0) {
+                    boolean isExcluded = (taskInfo1.baseIntent.getFlags()
+                            & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0;
+                    if (taskInfo1.isTopActivityTransparent && isExcluded) {
+                        // If there are already visible tasks, then ignore the excluded tasks and
+                        // don't add them to the returned list
+                        continue;
+                    }
+                }
+            }
+            if (taskInfo1.isVisible) {
+                numVisibleTasks++;
             }
             final SplitConfigurationOptions.SplitBounds launcherSplitBounds =
                     convertShellSplitBoundsToLauncher(rawTask.getSplitBounds());
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
index 3d332c2..5d26ec0 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -115,7 +115,8 @@
                     /* extras= */ 0,
                     /* gestureEvent= */ ON_START_RECENTS_ANIMATION);
             notifyAnimationCanceled();
-            animationController.finish(false /* toHome */, false /* sendUserLeaveHint */);
+            animationController.finish(false /* toHome */, false /* sendUserLeaveHint */,
+                    null /* finishCb */);
             return;
         }
 
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 8972dc8..341e18c 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -21,6 +21,7 @@
 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.FINISH_RECENTS_ANIMATION;
 
 import android.content.Context;
+import android.os.Bundle;
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.IRecentsAnimationController;
@@ -32,6 +33,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.UiThread;
 
+import com.android.internal.os.IResultReceiver;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.RunnableList;
 import com.android.quickstep.util.ActiveGestureErrorDetector;
@@ -172,12 +174,19 @@
         mFinishTargetIsLauncher = toRecents;
         mOnFinishedListener.accept(this);
         Runnable finishCb = () -> {
-            mController.finish(toRecents, sendUserLeaveHint);
+            mController.finish(toRecents, sendUserLeaveHint, new IResultReceiver.Stub() {
+                @Override
+                public void send(int i, Bundle bundle) throws RemoteException {
+                    ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation-callback");
+                    MAIN_EXECUTOR.execute(() -> {
+                        mPendingFinishCallbacks.executeAllAndDestroy();
+                    });
+                }
+            });
             InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
             InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME);
             InteractionJankMonitorWrapper.end(
                     InteractionJankMonitorWrapper.CUJ_APP_SWIPE_TO_RECENTS);
-            MAIN_EXECUTOR.execute(mPendingFinishCallbacks::executeAllAndDestroy);
         };
         if (forceFinish) {
             finishCb.run();
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 1448a52..3fdc7ba 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -66,6 +66,7 @@
 import com.android.launcher3.util.NavigationMode;
 import com.android.launcher3.util.SettingsCache;
 import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
+import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.NavBarPosition;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -239,6 +240,7 @@
     public void onDisplayInfoChanged(Context context, Info info, int flags) {
         if ((flags & (CHANGE_ROTATION | CHANGE_NAVIGATION_MODE)) != 0) {
             mMode = info.navigationMode;
+            ActiveGestureLog.INSTANCE.setIsFullyGesturalNavMode(isFullyGesturalNavMode());
             mNavBarPosition = new NavBarPosition(mMode, info);
 
             if (mMode == NO_BUTTON) {
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 36a6eb6..89351aa 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -17,7 +17,7 @@
 
 import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
 
-import static com.android.launcher3.config.FeatureFlags.enableGridOnlyOverview;
+import static com.android.launcher3.Flags.enableGridOnlyOverview;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
 
@@ -78,6 +78,8 @@
     private final TaskThumbnailCache mThumbnailCache;
     private final ComponentCallbacks mCallbacks;
 
+    private final TaskStackChangeListeners mTaskStackChangeListeners;
+
     private RecentsModel(Context context) {
         this(context, new IconProvider(context));
     }
@@ -89,13 +91,14 @@
                         SystemUiProxy.INSTANCE.get(context)),
                 new TaskIconCache(context, RECENTS_MODEL_EXECUTOR, iconProvider),
                 new TaskThumbnailCache(context, RECENTS_MODEL_EXECUTOR),
-                iconProvider);
+                iconProvider,
+                TaskStackChangeListeners.getInstance());
     }
 
     @VisibleForTesting
     RecentsModel(Context context, RecentTasksList taskList, TaskIconCache iconCache,
-            TaskThumbnailCache thumbnailCache,
-            IconProvider iconProvider) {
+            TaskThumbnailCache thumbnailCache, IconProvider iconProvider,
+            TaskStackChangeListeners taskStackChangeListeners) {
         mContext = context;
         mTaskList = taskList;
         mIconCache = iconCache;
@@ -117,7 +120,8 @@
             mCallbacks = null;
         }
 
-        TaskStackChangeListeners.getInstance().registerTaskStackListener(this);
+        mTaskStackChangeListeners = taskStackChangeListeners;
+        mTaskStackChangeListeners.registerTaskStackListener(this);
         iconProvider.registerIconChangeListener(this, MAIN_EXECUTOR.getHandler());
     }
 
@@ -358,6 +362,8 @@
         if (mCallbacks != null) {
             mContext.unregisterComponentCallbacks(mCallbacks);
         }
+        mIconCache.removeTaskVisualsChangeListener();
+        mTaskStackChangeListeners.unregisterTaskStackListener(this);
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 419824a..56765e5 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -78,7 +78,7 @@
 import com.android.wm.shell.back.IBackAnimation;
 import com.android.wm.shell.bubbles.IBubbles;
 import com.android.wm.shell.bubbles.IBubblesListener;
-import com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition;
+import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
 import com.android.wm.shell.desktopmode.IDesktopMode;
 import com.android.wm.shell.desktopmode.IDesktopTaskListener;
 import com.android.wm.shell.draganddrop.IDragAndDrop;
@@ -799,7 +799,7 @@
 
     /** Start multiple tasks in split-screen simultaneously. */
     public void startTasks(int taskId1, Bundle options1, int taskId2, Bundle options2,
-            @StagePosition int splitPosition, @SnapPosition int snapPosition,
+            @StagePosition int splitPosition, @PersistentSnapPosition int snapPosition,
             RemoteTransition remoteTransition, InstanceId instanceId) {
         if (mSystemUiProxy != null) {
             try {
@@ -813,7 +813,7 @@
 
     public void startIntentAndTask(PendingIntent pendingIntent, int userId1, Bundle options1,
             int taskId, Bundle options2, @StagePosition int splitPosition,
-            @SnapPosition int snapPosition, RemoteTransition remoteTransition,
+            @PersistentSnapPosition int snapPosition, RemoteTransition remoteTransition,
             InstanceId instanceId) {
         if (mSystemUiProxy != null) {
             try {
@@ -828,7 +828,7 @@
     public void startIntents(PendingIntent pendingIntent1, int userId1,
             @Nullable ShortcutInfo shortcutInfo1, Bundle options1, PendingIntent pendingIntent2,
             int userId2, @Nullable ShortcutInfo shortcutInfo2, Bundle options2,
-            @StagePosition int splitPosition, @SnapPosition int snapPosition,
+            @StagePosition int splitPosition, @PersistentSnapPosition int snapPosition,
             RemoteTransition remoteTransition, InstanceId instanceId) {
         if (mSystemUiProxy != null) {
             try {
@@ -842,8 +842,9 @@
     }
 
     public void startShortcutAndTask(ShortcutInfo shortcutInfo, Bundle options1, int taskId,
-            Bundle options2, @StagePosition int splitPosition, @SnapPosition int snapPosition,
-            RemoteTransition remoteTransition, InstanceId instanceId) {
+            Bundle options2, @StagePosition int splitPosition,
+            @PersistentSnapPosition int snapPosition, RemoteTransition remoteTransition,
+            InstanceId instanceId) {
         if (mSystemUiProxy != null) {
             try {
                 mSplitScreen.startShortcutAndTask(shortcutInfo, options1, taskId, options2,
@@ -858,8 +859,9 @@
      * Start multiple tasks in split-screen simultaneously.
      */
     public void startTasksWithLegacyTransition(int taskId1, Bundle options1, int taskId2,
-            Bundle options2, @StagePosition int splitPosition, @SnapPosition int snapPosition,
-            RemoteAnimationAdapter adapter, InstanceId instanceId) {
+            Bundle options2, @StagePosition int splitPosition,
+            @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
+            InstanceId instanceId) {
         if (mSystemUiProxy != null) {
             try {
                 mSplitScreen.startTasksWithLegacyTransition(taskId1, options1, taskId2, options2,
@@ -873,7 +875,8 @@
 
     public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, int userId1,
             Bundle options1, int taskId, Bundle options2, @StagePosition int splitPosition,
-            @SnapPosition int snapPosition, RemoteAnimationAdapter adapter, InstanceId instanceId) {
+            @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
+            InstanceId instanceId) {
         if (mSystemUiProxy != null) {
             try {
                 mSplitScreen.startIntentAndTaskWithLegacyTransition(pendingIntent, userId1,
@@ -888,7 +891,8 @@
 
     public void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo, Bundle options1,
             int taskId, Bundle options2, @StagePosition int splitPosition,
-            @SnapPosition int snapPosition, RemoteAnimationAdapter adapter, InstanceId instanceId) {
+            @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
+            InstanceId instanceId) {
         if (mSystemUiProxy != null) {
             try {
                 mSplitScreen.startShortcutAndTaskWithLegacyTransition(shortcutInfo, options1,
@@ -908,7 +912,8 @@
             @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
             PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2,
             @Nullable Bundle options2, @StagePosition int sidePosition,
-            @SnapPosition int snapPosition, RemoteAnimationAdapter adapter, InstanceId instanceId) {
+            @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
+            InstanceId instanceId) {
         if (mSystemUiProxy != null) {
             try {
                 mSplitScreen.startIntentsWithLegacyTransition(pendingIntent1, userId1,
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index f5a7ecc..4b47209 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -42,7 +42,6 @@
 import com.android.launcher3.util.DisplayController;
 import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
 import com.android.quickstep.util.ActiveGestureLog;
-import com.android.quickstep.views.DesktopTaskView;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -119,7 +118,8 @@
             }
         }
         // But force-finish it anyways
-        finishRunningRecentsAnimation(false /* toHome */, true /* forceFinish */);
+        finishRunningRecentsAnimation(false /* toHome */, true /* forceFinish */,
+                null /* forceFinishCb */);
 
         if (mCallbacks != null) {
             // If mCallbacks still != null, that means we are getting this startRecentsAnimation()
@@ -261,12 +261,6 @@
             // to let the transition controller collect Home activity.
             CachedTaskInfo cti = gestureState.getRunningTask();
             boolean homeIsOnTop = cti != null && cti.isHomeTask();
-            if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
-                if (cti != null && cti.isFreeformTask()) {
-                    // No transient launch when desktop task is on top
-                    homeIsOnTop = true;
-                }
-            }
             if (activityInterface.allowAllAppsFromOverview()) {
                 homeIsOnTop = true;
             }
@@ -325,19 +319,20 @@
      * Finishes the running recents animation.
      */
     public void finishRunningRecentsAnimation(boolean toHome) {
-        finishRunningRecentsAnimation(toHome, false /* forceFinish */);
+        finishRunningRecentsAnimation(toHome, false /* forceFinish */, null /* forceFinishCb */);
     }
 
     /**
      * Finishes the running recents animation.
      * @param forceFinish will synchronously finish the controller
      */
-    public void finishRunningRecentsAnimation(boolean toHome, boolean forceFinish) {
+    public void finishRunningRecentsAnimation(boolean toHome, boolean forceFinish,
+            Runnable forceFinishCb) {
         if (mController != null) {
             ActiveGestureLog.INSTANCE.addLog(
                     /* event= */ "finishRunningRecentsAnimation", toHome);
             if (forceFinish) {
-                mController.finishController(toHome, null, false /* sendUserLeaveHint */,
+                mController.finishController(toHome, forceFinishCb, false /* sendUserLeaveHint */,
                         true /* forceFinish */);
             } else {
                 Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), toHome
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index bd3ccb7..7d03d77 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -34,8 +34,8 @@
 
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.Flags;
 import com.android.launcher3.R;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.popup.SystemShortcut;
@@ -80,7 +80,7 @@
         boolean isInLandscape = orientedState.getTouchRotation() != ROTATION_0;
         boolean isTablet = activity.getDeviceProfile().isTablet;
 
-        boolean isGridOnlyOverview = isTablet && FeatureFlags.enableGridOnlyOverview();
+        boolean isGridOnlyOverview = isTablet && Flags.enableGridOnlyOverview();
         // Add overview actions to the menu when in in-place rotate landscape mode, or in
         // grid-only overview.
         if ((!canLauncherRotate && isInLandscape) || isGridOnlyOverview) {
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index 2adc790..e8adcab 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -50,6 +50,7 @@
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.util.InstantAppResolver;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
+import com.android.quickstep.views.GroupedTaskView;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskThumbnailView;
 import com.android.quickstep.views.TaskView;
@@ -128,12 +129,12 @@
 
     /**
      * A menu item, "Save app pair", that allows the user to preserve the current app combination as
-     * a single persistent icon on the Home screen, allowing for quick split screen initialization.
+     * one persistent icon on the Home screen, allowing for quick split screen launching.
      */
     class SaveAppPairSystemShortcut extends SystemShortcut<BaseDraggingActivity> {
-        private final TaskView mTaskView;
+        private final GroupedTaskView mTaskView;
 
-        public SaveAppPairSystemShortcut(BaseDraggingActivity activity, TaskView taskView) {
+        public SaveAppPairSystemShortcut(BaseDraggingActivity activity, GroupedTaskView taskView) {
             super(R.drawable.ic_save_app_pair, R.string.save_app_pair, activity,
                     taskView.getItemInfo(), taskView);
             mTaskView = taskView;
@@ -314,11 +315,12 @@
                 TaskIdAttributeContainer taskContainer) {
             final TaskView taskView = taskContainer.getTaskView();
 
-            if (!FeatureFlags.ENABLE_APP_PAIRS.get() || !taskView.containsMultipleTasks()) {
+            if (!FeatureFlags.enableAppPairs() || !taskView.containsMultipleTasks()) {
                 return null;
             }
 
-            return Collections.singletonList(new SaveAppPairSystemShortcut(activity, taskView));
+            return Collections.singletonList(
+                    new SaveAppPairSystemShortcut(activity, (GroupedTaskView) taskView));
         }
 
         @Override
diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
index 2ca9f99..e5fca4b 100644
--- a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
+++ b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
@@ -15,7 +15,7 @@
  */
 package com.android.quickstep;
 
-import static com.android.launcher3.config.FeatureFlags.enableGridOnlyOverview;
+import static com.android.launcher3.Flags.enableGridOnlyOverview;
 
 import android.content.Context;
 import android.content.res.Resources;
diff --git a/quickstep/src/com/android/quickstep/TopTaskTracker.java b/quickstep/src/com/android/quickstep/TopTaskTracker.java
index 01baed3..f1af2ed 100644
--- a/quickstep/src/com/android/quickstep/TopTaskTracker.java
+++ b/quickstep/src/com/android/quickstep/TopTaskTracker.java
@@ -16,7 +16,6 @@
 package com.android.quickstep;
 
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
@@ -229,12 +228,21 @@
         }
 
         /**
-         * Returns true if the given task holds an Assistant activity that is excluded from recents
+         * If the given task holds an activity that is excluded from recents, and there
+         * is another running task that is not excluded from recents, returns that underlying task.
          */
-        public boolean isExcludedAssistant() {
-            return mTopTask != null && mTopTask.configuration.windowConfiguration
-                    .getActivityType() == ACTIVITY_TYPE_ASSISTANT
-                    && (mTopTask.baseIntent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0;
+        public @Nullable CachedTaskInfo otherVisibleTaskThisIsExcludedOver() {
+            if (mTopTask == null
+                    || (mTopTask.baseIntent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0) {
+                // Not an excluded task.
+                return null;
+            }
+            List<RunningTaskInfo> visibleNonExcludedTasks = mAllCachedTasks.stream()
+                    .filter(t -> t.isVisible
+                            && (t.baseIntent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0)
+                    .toList();
+            return visibleNonExcludedTasks.isEmpty() ? null
+                    : new CachedTaskInfo(visibleNonExcludedTasks);
         }
 
         /**
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 02fcc68..b2f04b8 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -24,10 +24,12 @@
 import static android.view.MotionEvent.ACTION_UP;
 
 import static com.android.launcher3.Launcher.INTENT_ACTION_ALL_APPS_TOGGLE;
+import static com.android.launcher3.LauncherPrefs.backedUpItem;
 import static com.android.launcher3.MotionEventsUtils.isTrackpadMotionEvent;
 import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_TRACKPAD_GESTURE;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.OnboardingPrefs.HOME_BOUNCE_SEEN;
 import static com.android.launcher3.util.window.WindowManagerProxy.MIN_TABLET_WIDTH;
 import static com.android.quickstep.GestureState.DEFAULT_STATE;
 import static com.android.quickstep.GestureState.TrackpadGestureType.getTrackpadGestureType;
@@ -60,7 +62,6 @@
 import android.content.IIntentReceiver;
 import android.content.IIntentSender;
 import android.content.Intent;
-import android.content.SharedPreferences;
 import android.content.res.Configuration;
 import android.graphics.Region;
 import android.graphics.drawable.Icon;
@@ -84,7 +85,9 @@
 import androidx.annotation.UiThread;
 
 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;
 import com.android.launcher3.anim.AnimatedFloat;
@@ -100,7 +103,6 @@
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.LockedUserState;
-import com.android.launcher3.util.OnboardingPrefs;
 import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.ScreenOnTracker;
 import com.android.launcher3.util.TraceHelper;
@@ -158,7 +160,8 @@
 
     private static final String TAG = "TouchInteractionService";
 
-    private static final String HAS_ENABLED_QUICKSTEP_ONCE = "launcher.has_enabled_quickstep_once";
+    private static final ConstantItem<Boolean> HAS_ENABLED_QUICKSTEP_ONCE = backedUpItem(
+            "launcher.has_enabled_quickstep_once", false, EncryptionType.ENCRYPTED);
 
     private final TISBinder mTISBinder = new TISBinder(this);
 
@@ -569,12 +572,11 @@
         }
 
         // Reset home bounce seen on quick step enabled for first time
-        SharedPreferences sharedPrefs = LauncherPrefs.getPrefs(this);
-        if (!sharedPrefs.getBoolean(HAS_ENABLED_QUICKSTEP_ONCE, true)) {
-            sharedPrefs.edit()
-                    .putBoolean(HAS_ENABLED_QUICKSTEP_ONCE, true)
-                    .putBoolean(OnboardingPrefs.HOME_BOUNCE_SEEN, false)
-                    .apply();
+        LauncherPrefs prefs = LauncherPrefs.get(this);
+        if (!prefs.get(HAS_ENABLED_QUICKSTEP_ONCE)) {
+            prefs.put(
+                    HAS_ENABLED_QUICKSTEP_ONCE.to(true),
+                    HOME_BOUNCE_SEEN.to(false));
         }
     }
 
@@ -1079,13 +1081,19 @@
         boolean forceOverviewInputConsumer = gestureState.getActivityInterface().isStarted()
                 && gestureState.getRunningTask() != null
                 && gestureState.getRunningTask().isRootChooseActivity();
-        if (gestureState.getRunningTask() != null
-                && gestureState.getRunningTask().isExcludedAssistant()) {
-            // In the case where we are in the excluded assistant state, ignore it and treat the
-            // running activity as the task behind the assistant
-            gestureState.updateRunningTask(TopTaskTracker.INSTANCE.get(this)
-                    .getCachedTopTask(true /* filterOnlyVisibleRecents */));
-            forceOverviewInputConsumer = gestureState.getRunningTask().isHomeTask();
+
+        // In the case where we are in an excluded, translucent overlay, ignore it and treat the
+        // running activity as the task behind the overlay.
+        TopTaskTracker.CachedTaskInfo otherVisibleTask = gestureState.getRunningTask() == null
+                ? null
+                : gestureState.getRunningTask().otherVisibleTaskThisIsExcludedOver();
+        if (otherVisibleTask != null) {
+            ActiveGestureLog.INSTANCE.addLog(new CompoundString("Changing active task to ")
+                    .append(otherVisibleTask.getPackageName())
+                    .append(" because the previous task running on top of this one (")
+                    .append(gestureState.getRunningTask().getPackageName())
+                    .append(") was excluded from recents"));
+            gestureState.updateRunningTask(otherVisibleTask);
         }
 
         boolean previousGestureAnimatedToLauncher =
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
index 63771f0..5557639 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
@@ -48,6 +48,14 @@
      */
     protected abstract String getDelegatorName();
 
+    @Override
+    public <T extends InputConsumer> T getInputConsumerOfClass(Class<T> c) {
+        if (getClass().equals(c)) {
+            return c.cast(this);
+        }
+        return mDelegate.getInputConsumerOfClass(c);
+    }
+
     protected void setActive(MotionEvent ev) {
         ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(getDelegatorName())
                 .append(" became active"));
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
index addcfb8..f9f1579 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
@@ -15,14 +15,19 @@
  */
 package com.android.quickstep.inputconsumers;
 
+import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE;
+import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_TIMEOUT_MS;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
 import android.content.Context;
 import android.view.GestureDetector;
 import android.view.GestureDetector.SimpleOnGestureListener;
 import android.view.MotionEvent;
+import android.view.ViewConfiguration;
 
+import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.R;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.DisplayController;
 import com.android.quickstep.InputConsumer;
 import com.android.systemui.shared.system.InputMonitorCompat;
@@ -37,24 +42,42 @@
     private final float mNavHandleWidth;
     private final float mScreenWidth;
 
+    // Below are only used if CUSTOM_LPNH_THRESHOLDS is enabled.
+    private final float mCustomTouchSlopSquared;
+    private final int mCustomLongPressTimeout;
+    private final Runnable mTriggerCustomLongPress = this::triggerCustomLongPress;
+    private MotionEvent mCurrentCustomDownEvent;
+
     public NavHandleLongPressInputConsumer(Context context, InputConsumer delegate,
             InputMonitorCompat inputMonitor) {
         super(delegate, inputMonitor);
         mNavHandleWidth = context.getResources().getDimensionPixelSize(
                 R.dimen.navigation_home_handle_width);
         mScreenWidth = DisplayController.INSTANCE.get(context).getInfo().currentSize.x;
+        float customSlopMultiplier =
+                LauncherPrefs.get(context).get(LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE) / 100f;
+        float customTouchSlop =
+                ViewConfiguration.get(context).getScaledEdgeSlop() * customSlopMultiplier;
+        mCustomTouchSlopSquared = customTouchSlop * customTouchSlop;
+        mCustomLongPressTimeout = LauncherPrefs.get(context).get(LONG_PRESS_NAV_HANDLE_TIMEOUT_MS);
 
         mNavHandleLongPressHandler = NavHandleLongPressHandler.newInstance(context);
 
         mLongPressDetector = new GestureDetector(context, new SimpleOnGestureListener() {
             @Override
             public void onLongPress(MotionEvent motionEvent) {
-                if (isInArea(motionEvent.getRawX())) {
+                if (isInNavBarHorizontalArea(motionEvent.getRawX())) {
                     Runnable longPressRunnable = mNavHandleLongPressHandler.getLongPressRunnable();
                     if (longPressRunnable != null) {
-                        setActive(motionEvent);
-
-                        MAIN_EXECUTOR.post(longPressRunnable);
+                        OtherActivityInputConsumer oaic = getInputConsumerOfClass(
+                                OtherActivityInputConsumer.class);
+                        if (oaic != null) {
+                            oaic.setForceFinishRecentsTransitionCallback(longPressRunnable);
+                            setActive(motionEvent);
+                        } else {
+                            setActive(motionEvent);
+                            MAIN_EXECUTOR.post(longPressRunnable);
+                        }
                     }
                 }
             }
@@ -68,13 +91,51 @@
 
     @Override
     public void onMotionEvent(MotionEvent ev) {
-        mLongPressDetector.onTouchEvent(ev);
+        if (!FeatureFlags.CUSTOM_LPNH_THRESHOLDS.get()) {
+            mLongPressDetector.onTouchEvent(ev);
+        } else {
+            switch (ev.getAction()) {
+                case MotionEvent.ACTION_DOWN -> {
+                    if (mCurrentCustomDownEvent != null) {
+                        mCurrentCustomDownEvent.recycle();
+                    }
+                    mCurrentCustomDownEvent = MotionEvent.obtain(ev);
+                    if (isInNavBarHorizontalArea(ev.getRawX())) {
+                        MAIN_EXECUTOR.getHandler().postDelayed(mTriggerCustomLongPress,
+                                mCustomLongPressTimeout);
+                    }
+                }
+                case MotionEvent.ACTION_MOVE -> {
+                    double touchDeltaSquared =
+                            Math.pow(ev.getX() - mCurrentCustomDownEvent.getX(), 2)
+                            + Math.pow(ev.getY() - mCurrentCustomDownEvent.getY(), 2);
+                    if (touchDeltaSquared > mCustomTouchSlopSquared) {
+                        cancelCustomLongPress();
+                    }
+                }
+                case MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> cancelCustomLongPress();
+            }
+        }
+
         if (mState != STATE_ACTIVE) {
             mDelegate.onMotionEvent(ev);
         }
     }
 
-    protected boolean isInArea(float x) {
+    private void triggerCustomLongPress() {
+        Runnable longPressRunnable = mNavHandleLongPressHandler.getLongPressRunnable();
+        if (longPressRunnable != null) {
+            setActive(mCurrentCustomDownEvent);
+
+            MAIN_EXECUTOR.post(longPressRunnable);
+        }
+    }
+
+    private void cancelCustomLongPress() {
+        MAIN_EXECUTOR.getHandler().removeCallbacks(mTriggerCustomLongPress);
+    }
+
+    private boolean isInNavBarHorizontalArea(float x) {
         float areaFromMiddle = mNavHandleWidth / 2.0f;
         float distFromMiddle = Math.abs(mScreenWidth / 2.0f - x);
 
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index 7e61167..28c00eb 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -125,6 +125,9 @@
     // Might be displacement in X or Y, depending on the direction we are swiping from the nav bar.
     private float mStartDisplacement;
 
+    // The callback called upon finishing the recents transition if it was force-canceled
+    private Runnable mForceFinishRecentsTransitionCallback;
+
     public OtherActivityInputConsumer(Context base, RecentsAnimationDeviceState deviceState,
             TaskAnimationManager taskAnimationManager, GestureState gestureState,
             boolean isDeferredDownTarget, Consumer<OtherActivityInputConsumer> onCompleteCallback,
@@ -444,7 +447,7 @@
                     // animateToProgress so we have to manually finish here. In the case of
                     // ACTION_CANCEL, someone else may be doing something so finish synchronously.
                     mTaskAnimationManager.finishRunningRecentsAnimation(false /* toHome */,
-                            isCanceled /* forceFinish */);
+                            isCanceled /* forceFinish */, mForceFinishRecentsTransitionCallback);
                 } else {
                     // The animation hasn't started yet, so insert a replacement handler into the
                     // callbacks which immediately finishes the animation after it starts.
@@ -513,6 +516,14 @@
     }
 
     /**
+     * Sets a callback to be called when the recents transition is force-canceled by another input
+     * consumer being made active.
+     */
+    public void setForceFinishRecentsTransitionCallback(Runnable r) {
+        mForceFinishRecentsTransitionCallback = r;
+    }
+
+    /**
      * A listener which just finishes the animation immediately after starting. Replaces
      * AbsSwipeUpHandler if the gesture itself finishes before the animation even starts.
      */
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
index 3388642..7d3a860 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
@@ -21,7 +21,9 @@
 import android.media.session.MediaSessionManager;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
+import android.view.View;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.Utilities;
@@ -52,6 +54,7 @@
     private final boolean mStartingInActivityBounds;
     private boolean mTargetHandledTouch;
     private boolean mHasSetTouchModeForFirstDPadEvent;
+    private boolean mIsWaitingForAttachToWindow;
 
     public OverviewInputConsumer(GestureState gestureState, T activity,
             @Nullable InputMonitorCompat inputMonitor, boolean startingInActivityBounds) {
@@ -118,21 +121,47 @@
                 break;
             case KeyEvent.KEYCODE_DPAD_LEFT:
             case KeyEvent.KEYCODE_DPAD_RIGHT:
-                if (!mHasSetTouchModeForFirstDPadEvent) {
-                    // When Overview is launched via meta+tab or swipe up from an app, the touch
-                    // mode somehow is not changed to false by the Android framework. The subsequent
-                    // key events (e.g. DPAD_LEFT, DPAD_RIGHT) can only be dispatched to focused
-                    // views, while focus can only be requested in
-                    // {@link View#requestFocusNoSearch(int, Rect)} when touch mode is false. To
-                    // note, here we launch overview with live tile.
-                    mHasSetTouchModeForFirstDPadEvent = true;
-                    mActivity.getRootView().getViewRootImpl().touchModeChanged(false);
+                if (mHasSetTouchModeForFirstDPadEvent) {
+                    break;
                 }
+                View viewRoot = mActivity.getRootView();
+                if (viewRoot.isAttachedToWindow()) {
+                    setTouchModeChanged(viewRoot);
+                    break;
+                }
+                if (mIsWaitingForAttachToWindow) {
+                    break;
+                }
+                mIsWaitingForAttachToWindow = true;
+                viewRoot.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+                    @Override
+                    public void onViewAttachedToWindow(View view) {
+                        view.removeOnAttachStateChangeListener(this);
+                        mIsWaitingForAttachToWindow = false;
+                        setTouchModeChanged(viewRoot);
+                    }
+
+                    @Override
+                    public void onViewDetachedFromWindow(View view) {
+                        // Do nothing
+                    }
+                });
                 break;
             default:
                 break;
         }
         mActivity.dispatchKeyEvent(ev);
     }
+
+    private void setTouchModeChanged(@NonNull View viewRoot) {
+        // When Overview is launched via meta+tab or swipe up from an app, the touch
+        // mode somehow is not changed to false by the Android framework. The
+        // subsequent key events (e.g. DPAD_LEFT, DPAD_RIGHT) can only be dispatched
+        // to focused views, while focus can only be requested in
+        // {@link View#requestFocusNoSearch(int, Rect)} when touch mode is false. To
+        // note, here we launch overview with live tile.
+        mHasSetTouchModeForFirstDPadEvent = true;
+        viewRoot.getViewRootImpl().touchModeChanged(false);
+    }
 }
 
diff --git a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
index d3a01f2..f8d695c 100644
--- a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
+++ b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
@@ -26,7 +26,7 @@
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_THEMED_ICON_DISABLED;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_THEMED_ICON_ENABLED;
 import static com.android.launcher3.model.DeviceGridState.KEY_WORKSPACE_SIZE;
-import static com.android.launcher3.model.QuickstepModelDelegate.LAST_PREDICTION_ENABLED_STATE;
+import static com.android.launcher3.model.PredictionUpdateTask.LAST_PREDICTION_ENABLED;
 import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
 import static com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI;
 import static com.android.launcher3.util.Themes.KEY_THEMED_ICONS;
@@ -155,13 +155,12 @@
 
     @Override
     public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
-        if (LAST_PREDICTION_ENABLED_STATE.equals(key)
+        if (LAST_PREDICTION_ENABLED.getSharedPrefKey().equals(key)
                 || KEY_WORKSPACE_SIZE.equals(key)
                 || KEY_THEMED_ICONS.equals(key)
                 || mLoggablePrefs.containsKey(key)) {
 
-            mHomeScreenSuggestionEvent = getDevicePrefs(mContext)
-                    .getBoolean(LAST_PREDICTION_ENABLED_STATE, true)
+            mHomeScreenSuggestionEvent = LauncherPrefs.get(mContext).get(LAST_PREDICTION_ENABLED)
                     ? LAUNCHER_HOME_SCREEN_SUGGESTIONS_ENABLED
                     : LAUNCHER_HOME_SCREEN_SUGGESTIONS_DISABLED;
 
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
index 20fa921..ee72144 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
@@ -74,6 +74,11 @@
             @NonNull PrintWriter writer,
             @NonNull ActiveGestureLog.EventLog eventLog) {
         writer.println(prefix + "Error messages for gesture ID: " + eventLog.logId);
+        if (!eventLog.mIsFullyGesturalNavMode) {
+            writer.println(prefix
+                    + "\tSkipping gesture error detection because gesture navigation not enabled");
+            return;
+        }
 
         boolean errorDetected = false;
         // Use a Set since the order is inherently checked in the loop.
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
index 7103e63..80dd373 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
@@ -31,10 +31,12 @@
  */
 public class ActiveGestureLog {
 
-    private static final int MAX_GESTURES_TRACKED = 10;
+    private static final int MAX_GESTURES_TRACKED = 15;
 
     public static final ActiveGestureLog INSTANCE = new ActiveGestureLog();
 
+    private boolean mIsFullyGesturalNavMode;
+
     /**
      * NOTE: This value should be kept same as
      * ActivityTaskManagerService#INTENT_EXTRA_LOG_TRACE_ID in platform
@@ -127,7 +129,7 @@
             @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
         EventLog lastEventLog = logs[(nextIndex + logs.length - 1) % logs.length];
         if (lastEventLog == null || mCurrentLogId != lastEventLog.logId) {
-            EventLog eventLog = new EventLog(mCurrentLogId);
+            EventLog eventLog = new EventLog(mCurrentLogId, mIsFullyGesturalNavMode);
             EventEntry eventEntry = new EventEntry();
 
             eventEntry.update(type, event, extras, compoundString, gestureEvent);
@@ -214,6 +216,10 @@
         return mCurrentLogId++;
     }
 
+    public void setIsFullyGesturalNavMode(boolean isFullyGesturalNavMode) {
+        mIsFullyGesturalNavMode = isFullyGesturalNavMode;
+    }
+
     /** Returns the current log ID. This should be used when a log trace is being reused. */
     public int getLogId() {
         return mCurrentLogId;
@@ -277,9 +283,11 @@
 
         protected final List<EventEntry> eventEntries = new ArrayList<>();
         protected final int logId;
+        protected final boolean mIsFullyGesturalNavMode;
 
-        private EventLog(int logId) {
+        private EventLog(int logId, boolean isFullyGesturalNavMode) {
             this.logId = logId;
+            mIsFullyGesturalNavMode = isFullyGesturalNavMode;
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/util/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java
index 1a7099d..cc3b54b 100644
--- a/quickstep/src/com/android/quickstep/util/AppPairsController.java
+++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java
@@ -20,48 +20,48 @@
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_PAIR_LAUNCH;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.isPersistentSnapPosition;
 
 import android.app.ActivityTaskManager;
 import android.content.Context;
 import android.content.Intent;
 
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.R;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.SplitConfigurationOptions;
+import com.android.quickstep.views.GroupedTaskView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.Task;
+import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
 
 import java.util.Arrays;
 
 /**
- * Mini controller class that handles app pair interactions: saving, modifying, deleting, etc.
+ * Controller class that handles app pair interactions: saving, modifying, deleting, etc.
+ * <br>
+ * App pairs contain two "member" apps, which are determined at the time of app pair creation
+ * and never modified. The member apps are WorkspaceItemInfos, but use the "rank" attribute
+ * differently from other ItemInfos -- we use it to store information about the split position and
+ * ratio.
  */
 public class AppPairsController {
+    // Used for encoding and decoding the "rank" attribute
+    private static final int BITMASK_SIZE = 16;
+    private static final int BITMASK_FOR_SNAP_POSITION = (1 << BITMASK_SIZE) - 1;
 
-    private static final int POINT_THREE_RATIO = 0;
-    private static final int POINT_FIVE_RATIO = 1;
-    private static final int POINT_SEVEN_RATIO = 2;
-    /**
-     * Used to calculate {@link #complement(int)}
-     */
-    private static final int FULL_RATIO = 2;
-
-    private static final int LEFT_TOP = 0;
-    private static final int RIGHT_BOTTOM = 1 << 2;
-
-    // TODO (jeremysim b/274189428): Support saving different ratios in future.
-    public int DEFAULT_RATIO = POINT_FIVE_RATIO;
-
-    private final Context mContext;
+    private Context mContext;
     private final SplitSelectStateController mSplitSelectStateController;
     private final StatsLogManager mStatsLogManager;
     public AppPairsController(Context context,
@@ -72,20 +72,28 @@
         mStatsLogManager = statsLogManager;
     }
 
+    void onDestroy() {
+        mContext = null;
+    }
+
     /**
      * Creates a new app pair ItemInfo and adds it to the workspace
      */
-    public void saveAppPair(TaskView taskView) {
-        TaskView.TaskIdAttributeContainer[] attributes = taskView.getTaskIdAttributeContainers();
+    public void saveAppPair(GroupedTaskView gtv) {
+        TaskView.TaskIdAttributeContainer[] attributes = gtv.getTaskIdAttributeContainers();
         WorkspaceItemInfo app1 = attributes[0].getItemInfo().clone();
         WorkspaceItemInfo app2 = attributes[1].getItemInfo().clone();
         app1.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
         app2.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
-        app1.rank = DEFAULT_RATIO + LEFT_TOP;
-        app2.rank = complement(DEFAULT_RATIO) + RIGHT_BOTTOM;
+
+        @PersistentSnapPosition int snapPosition = gtv.getSnapPosition();
+        if (!isPersistentSnapPosition(snapPosition)) {
+            throw new RuntimeException("tried to save an app pair with illegal snapPosition");
+        }
+
+        app1.rank = encodeRank(SPLIT_POSITION_TOP_OR_LEFT, snapPosition);
+        app2.rank = encodeRank(SPLIT_POSITION_BOTTOM_OR_RIGHT, snapPosition);
         FolderInfo newAppPair = FolderInfo.createAppPair(app1, app2);
-        // TODO (jeremysim b/274189428): Generate default title here.
-        newAppPair.title = "App pair 1";
 
         IconCache iconCache = LauncherAppState.getInstance(mContext).getIconCache();
         MODEL_EXECUTOR.execute(() -> {
@@ -94,6 +102,8 @@
                 member.bitmap = iconCache.getDefaultIcon(newAppPair.user);
                 iconCache.getTitleAndIcon(member, member.usingLowResIcon());
             });
+            newAppPair.title = getDefaultTitle(newAppPair.contents.get(0).title,
+                    newAppPair.contents.get(1).title);
             MAIN_EXECUTOR.execute(() -> {
                 LauncherAccessibilityDelegate delegate =
                         Launcher.getLauncher(mContext).getAccessibilityDelegate();
@@ -128,7 +138,7 @@
                     }
 
                     mSplitSelectStateController.setInitialTaskSelect(task1Intent,
-                            SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
+                            AppPairsController.convertRankToStagePosition(app1.rank),
                             app1,
                             LAUNCHER_APP_PAIR_LAUNCH,
                             task1Id);
@@ -141,18 +151,42 @@
                                 app2.intent, app2.user);
                     }
 
-                    mSplitSelectStateController.launchSplitTasks();
+                    mSplitSelectStateController.launchSplitTasks(
+                            AppPairsController.convertRankToSnapPosition(app1.rank));
                 });
     }
 
     /**
-     * Used to calculate the "opposite" side of the split ratio, so we can know how big the split
-     * apps are supposed to be. This math works because POINT_THREE_RATIO is internally represented
-     * by 0, POINT_FIVE_RATIO is represented by 1, and POINT_SEVEN_RATIO is represented by 2. There
-     * are no other supported ratios for now.
+     * App pair members have a "rank" attribute that contains information about the split position
+     * and ratio. We implement this by splitting the int in half (e.g. 16 bits each), then use one
+     * half to store splitPosition (left vs right) and the other half to store snapPosition
+     * (30-70 split vs 50-50 split)
      */
-    private int complement(int ratio1) {
-        int ratio2 = FULL_RATIO - ratio1;
-        return ratio2;
+    @VisibleForTesting
+    public int encodeRank(int splitPosition, int snapPosition) {
+        return (splitPosition << BITMASK_SIZE) + snapPosition;
+    }
+
+    /**
+     * Returns the desired stage position for the app pair to be launched in (decoded from the
+     * "rank" integer).
+     */
+    public static int convertRankToStagePosition(int rank) {
+        return rank >> BITMASK_SIZE;
+    }
+
+    /**
+     * Returns the desired split ratio for the app pair to be launched in (decoded from the "rank"
+     * integer).
+     */
+    public static int convertRankToSnapPosition(int rank) {
+        return rank & BITMASK_FOR_SNAP_POSITION;
+    }
+
+    /**
+     * Returns a formatted default title for the app pair.
+     */
+    public String getDefaultTitle(CharSequence app1, CharSequence app2) {
+        return mContext.getString(R.string.app_pair_default_title, app1, app2);
     }
 }
diff --git a/quickstep/src/com/android/quickstep/util/AsyncClockEventDelegate.java b/quickstep/src/com/android/quickstep/util/AsyncClockEventDelegate.java
index 0dee5b3..cda87c0 100644
--- a/quickstep/src/com/android/quickstep/util/AsyncClockEventDelegate.java
+++ b/quickstep/src/com/android/quickstep/util/AsyncClockEventDelegate.java
@@ -32,6 +32,8 @@
 
 import androidx.annotation.WorkerThread;
 
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.SettingsCache;
 import com.android.launcher3.util.SettingsCache.OnChangeListener;
 import com.android.launcher3.util.SimpleBroadcastReceiver;
@@ -42,7 +44,11 @@
 /**
  * Extension of {@link ClockEventDelegate} to support async event registration
  */
-public class AsyncClockEventDelegate extends ClockEventDelegate implements OnChangeListener {
+public class AsyncClockEventDelegate extends ClockEventDelegate
+        implements OnChangeListener, SafeCloseable {
+
+    public static final MainThreadInitializedObject<AsyncClockEventDelegate> INSTANCE =
+            new MainThreadInitializedObject<>(AsyncClockEventDelegate::new);
 
     private final Context mContext;
     private final SimpleBroadcastReceiver mReceiver =
@@ -55,7 +61,7 @@
     private boolean mFormatRegistered = false;
     private boolean mDestroyed = false;
 
-    public AsyncClockEventDelegate(Context context) {
+    private AsyncClockEventDelegate(Context context) {
         super(context);
         mContext = context;
 
@@ -79,6 +85,9 @@
 
     @Override
     public void registerFormatChangeObserver(ContentObserver observer, int userHandle) {
+        if (mDestroyed) {
+            return;
+        }
         synchronized (mFormatObservers) {
             if (!mFormatRegistered && !mDestroyed) {
                 SettingsCache.INSTANCE.get(mContext).register(mFormatUri, this);
@@ -114,10 +123,8 @@
         }
     }
 
-    /**
-     * Unregisters all system callbacks and destroys this delegate
-     */
-    public void onDestroy() {
+    @Override
+    public void close() {
         mDestroyed = true;
         SettingsCache.INSTANCE.get(mContext).unregister(mFormatUri, this);
         UI_HELPER_EXECUTOR.execute(() -> mReceiver.unregisterReceiverSafely(mContext));
diff --git a/quickstep/src/com/android/quickstep/util/FadeOutRemoteTransition.kt b/quickstep/src/com/android/quickstep/util/FadeOutRemoteTransition.kt
index 59ff81d..5cce728 100644
--- a/quickstep/src/com/android/quickstep/util/FadeOutRemoteTransition.kt
+++ b/quickstep/src/com/android/quickstep/util/FadeOutRemoteTransition.kt
@@ -83,4 +83,7 @@
 
         Executors.MAIN_EXECUTOR.execute { anim.start() }
     }
+
+    override fun onTransitionConsumed(transition: IBinder?, aborted: Boolean) {
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
index ef7d7a9..9df568e 100644
--- a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
+++ b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
@@ -22,9 +22,12 @@
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.util.NavigationMode.NO_BUTTON;
+import static com.android.launcher3.util.OnboardingPrefs.ALL_APPS_VISITED_COUNT;
+import static com.android.launcher3.util.OnboardingPrefs.HOME_BOUNCE_COUNT;
+import static com.android.launcher3.util.OnboardingPrefs.HOME_BOUNCE_SEEN;
+import static com.android.launcher3.util.OnboardingPrefs.HOTSEAT_DISCOVERY_TIP_COUNT;
 
-import android.content.SharedPreferences;
-
+import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
@@ -34,30 +37,30 @@
 import com.android.launcher3.statemanager.StateManager.StateListener;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.OnboardingPrefs;
 import com.android.quickstep.views.AllAppsEduView;
 
 /**
- * Extends {@link OnboardingPrefs} for quickstep-specific onboarding data.
+ * Class to setup onboarding behavior for quickstep launcher
  */
-public class QuickstepOnboardingPrefs extends OnboardingPrefs<QuickstepLauncher> {
+public class QuickstepOnboardingPrefs {
 
-    public QuickstepOnboardingPrefs(QuickstepLauncher launcher, SharedPreferences sharedPrefs) {
-        super(launcher, sharedPrefs);
-
+    /**
+     * Sets up the initial onboarding behavior for the launcher
+     */
+    public static void setup(QuickstepLauncher launcher) {
         StateManager<LauncherState> stateManager = launcher.getStateManager();
-        if (!getBoolean(HOME_BOUNCE_SEEN)) {
+        if (!HOME_BOUNCE_SEEN.get(launcher)) {
             stateManager.addStateListener(new StateListener<LauncherState>() {
                 @Override
                 public void onStateTransitionComplete(LauncherState finalState) {
                     boolean swipeUpEnabled =
-                            DisplayController.getNavigationMode(mLauncher).hasGestures;
+                            DisplayController.getNavigationMode(launcher).hasGestures;
                     LauncherState prevState = stateManager.getLastState();
 
                     if (((swipeUpEnabled && finalState == OVERVIEW) || (!swipeUpEnabled
                             && finalState == ALL_APPS && prevState == NORMAL) ||
-                            hasReachedMaxCount(HOME_BOUNCE_COUNT))) {
-                        mSharedPrefs.edit().putBoolean(HOME_BOUNCE_SEEN, true).apply();
+                            HOME_BOUNCE_COUNT.hasReachedMax(launcher))) {
+                        LauncherPrefs.get(launcher).put(HOME_BOUNCE_SEEN, true);
                         stateManager.removeStateListener(this);
                     }
                 }
@@ -65,21 +68,21 @@
         }
 
         if (!Utilities.isRunningInTestHarness()
-                && !hasReachedMaxCount(HOTSEAT_DISCOVERY_TIP_COUNT)) {
+                && !HOTSEAT_DISCOVERY_TIP_COUNT.hasReachedMax(launcher)) {
             stateManager.addStateListener(new StateListener<LauncherState>() {
                 boolean mFromAllApps = false;
 
                 @Override
                 public void onStateTransitionStart(LauncherState toState) {
-                    mFromAllApps = mLauncher.getStateManager().getCurrentStableState() == ALL_APPS;
+                    mFromAllApps = launcher.getStateManager().getCurrentStableState() == ALL_APPS;
                 }
 
                 @Override
                 public void onStateTransitionComplete(LauncherState finalState) {
-                    HotseatPredictionController client = mLauncher.getHotseatPredictionController();
+                    HotseatPredictionController client = launcher.getHotseatPredictionController();
                     if (mFromAllApps && finalState == NORMAL && client.hasPredictions()) {
-                        if (!mLauncher.getDeviceProfile().isTablet
-                                && incrementEventCount(HOTSEAT_DISCOVERY_TIP_COUNT)) {
+                        if (!launcher.getDeviceProfile().isTablet
+                                && HOTSEAT_DISCOVERY_TIP_COUNT.increment(launcher)) {
                             client.showEdu();
                             stateManager.removeStateListener(this);
                         }
@@ -109,7 +112,7 @@
                 public void onStateTransitionComplete(LauncherState finalState) {
                     if (finalState == NORMAL) {
                         if (mCount >= MAX_NUM_SWIPES_TO_TRIGGER_EDU) {
-                            if (getOpenView(mLauncher, TYPE_ALL_APPS_EDU) == null) {
+                            if (getOpenView(launcher, TYPE_ALL_APPS_EDU) == null) {
                                 AllAppsEduView.show(launcher);
                             }
                             mCount = 0;
@@ -124,7 +127,7 @@
                     }
 
                     if (finalState == ALL_APPS) {
-                        AllAppsEduView view = getOpenView(mLauncher, TYPE_ALL_APPS_EDU);
+                        AllAppsEduView view = getOpenView(launcher, TYPE_ALL_APPS_EDU);
                         if (view != null) {
                             view.close(false);
                         }
@@ -133,20 +136,20 @@
             });
         }
 
-        if (!hasReachedMaxCount(ALL_APPS_VISITED_COUNT)) {
-            mLauncher.getStateManager().addStateListener(new StateListener<LauncherState>() {
+        if (!ALL_APPS_VISITED_COUNT.hasReachedMax(launcher)) {
+            launcher.getStateManager().addStateListener(new StateListener<LauncherState>() {
                 @Override
                 public void onStateTransitionComplete(LauncherState finalState) {
                     if (finalState == ALL_APPS) {
-                        incrementEventCount(ALL_APPS_VISITED_COUNT);
+                        ALL_APPS_VISITED_COUNT.increment(launcher);
                         return;
                     }
 
-                    boolean hasReachedMaxCount = hasReachedMaxCount(ALL_APPS_VISITED_COUNT);
-                    mLauncher.getAppsView().getFloatingHeaderView().findFixedRowByType(
+                    boolean hasReachedMaxCount = ALL_APPS_VISITED_COUNT.hasReachedMax(launcher);
+                    launcher.getAppsView().getFloatingHeaderView().findFixedRowByType(
                             AppsDividerView.class).setShowAllAppsLabel(!hasReachedMaxCount);
                     if (hasReachedMaxCount) {
-                        mLauncher.getStateManager().removeStateListener(this);
+                        launcher.getStateManager().removeStateListener(this);
                     }
                 }
             });
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index 689402b..ae497f0 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -30,6 +30,7 @@
 import com.android.launcher3.DeviceProfile
 import com.android.launcher3.Utilities
 import com.android.launcher3.anim.PendingAnimation
+import com.android.launcher3.config.FeatureFlags
 import com.android.launcher3.statemanager.StatefulActivity
 import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource
 import com.android.launcher3.views.BaseDragLayer
@@ -40,6 +41,7 @@
 import com.android.quickstep.views.TaskView
 import com.android.quickstep.views.TaskView.TaskIdAttributeContainer
 import com.android.quickstep.views.TaskViewIcon
+import java.util.Optional
 import java.util.function.Supplier
 
 /**
@@ -270,6 +272,45 @@
         safeRemoveViewFromDragLayer(launcher, splitSelectStateController.splitInstructionsView)
     }
 
+    /**
+     * Animates the first placeholder view to fullscreen and launches its task.
+     * TODO(b/276361926): Remove the [resetCallback] option once contextual launches
+     */
+    fun playAnimPlaceholderToFullscreen(launcher: StatefulActivity<*>, view: View,
+                                        resetCallback: Optional<Runnable>) {
+        val stagedTaskView = view as FloatingTaskView
+
+        val isTablet: Boolean = launcher.deviceProfile.isTablet
+        val duration = if (isTablet) SplitAnimationTimings.TABLET_CONFIRM_DURATION else
+            SplitAnimationTimings.PHONE_CONFIRM_DURATION
+        val pendingAnimation = PendingAnimation(duration.toLong())
+        val firstTaskStartingBounds = Rect()
+        val firstTaskEndingBounds = Rect()
+
+        stagedTaskView.getBoundsOnScreen(firstTaskStartingBounds)
+        launcher.dragLayer.getBoundsOnScreen(firstTaskEndingBounds)
+        splitSelectStateController.setLaunchingFirstAppFullscreen()
+
+        stagedTaskView.addConfirmAnimation(
+                pendingAnimation,
+                RectF(firstTaskStartingBounds),
+                firstTaskEndingBounds,
+                false /* fadeWithThumbnail */,
+                true /* isStagedTask */)
+
+        pendingAnimation.addEndListener {
+            splitSelectStateController.launchInitialAppFullscreen {
+                if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+                    splitSelectStateController.resetState()
+                } else if (resetCallback.isPresent) {
+                    resetCallback.get().run()
+                }
+            }
+        }
+
+        pendingAnimation.buildAnim().start()
+    }
+
     private fun safeRemoveViewFromDragLayer(launcher: StatefulActivity<*>, view: View?) {
         if (view != null) {
             launcher.dragLayer.removeView(view)
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt b/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt
index 95f1fbf..423ba43 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt
@@ -54,7 +54,7 @@
  * state
  */
 class SplitSelectDataHolder(
-        val context: Context
+        var context: Context?
 ) {
     val TAG = SplitSelectDataHolder::class.simpleName
 
@@ -100,6 +100,10 @@
     private var initialShortcut: ShortcutInfo? = null
     private var secondShortcut: ShortcutInfo? = null
 
+    fun onDestroy() {
+        context = null
+    }
+
     /**
      * @param alreadyRunningTask if set to [android.app.ActivityTaskManager.INVALID_TASK_ID]
      * then @param intent will be used to launch the initial task
@@ -164,18 +168,15 @@
     }
 
     private fun getShortcutInfo(intent: Intent?, user: UserHandle?): ShortcutInfo? {
-        val intentPackage = intent?.getPackage()
-        if (intentPackage == null) {
-            return null
-        }
+        val intentPackage = intent?.getPackage() ?: return null
         val shortcutId = intent.getStringExtra(ShortcutKey.EXTRA_SHORTCUT_ID)
                 ?: return null
         try {
             val context: Context =
                 if (user != null) {
-                    context.createPackageContextAsUser(intentPackage, 0 /* flags */, user)
+                    context!!.createPackageContextAsUser(intentPackage, 0 /* flags */, user)
                 } else {
-                    context.createPackageContext(intentPackage, 0 /* *flags */)
+                    context!!.createPackageContext(intentPackage, 0 /* *flags */)
                 }
             return ShortcutInfo.Builder(context, shortcutId).build()
         } catch (e: PackageManager.NameNotFoundException) {
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index c8831c7..8e3b5ec 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -17,7 +17,6 @@
 package com.android.quickstep.util;
 
 import static com.android.launcher3.Utilities.postAsyncCallback;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_DESKTOP_TO_WORKSPACE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_DESKTOP_MODE_SPLIT_LEFT_TOP;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_DESKTOP_MODE_SPLIT_RIGHT_BOTTOM;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
@@ -37,6 +36,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.annotation.NonNull;
+import android.annotation.UiThread;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.ActivityThread;
@@ -91,6 +91,7 @@
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TaskAnimationManager;
 import com.android.quickstep.TaskViewUtils;
+import com.android.quickstep.views.DesktopTaskView;
 import com.android.quickstep.views.FloatingTaskView;
 import com.android.quickstep.views.GroupedTaskView;
 import com.android.quickstep.views.RecentsView;
@@ -98,7 +99,7 @@
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
-import com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition;
+import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
 import com.android.wm.shell.splitscreen.ISplitSelectListener;
 
 import java.io.PrintWriter;
@@ -118,7 +119,7 @@
     private final Handler mHandler;
     private final RecentsModel mRecentTasksModel;
     @Nullable
-    private final Runnable mActivityBackCallback;
+    private Runnable mActivityBackCallback;
     private final SplitAnimationController mSplitAnimationController;
     private final AppPairsController mAppPairsController;
     private final SplitSelectDataHolder mSplitSelectDataHolder;
@@ -140,6 +141,9 @@
     @Nullable
     private GroupedTaskView mLaunchingTaskView;
 
+    /** True when the first selected split app is being launched in fullscreen. */
+    private boolean mLaunchingFirstAppFullscreen;
+
     private FloatingTaskView mFirstFloatingTaskView;
     private SplitInstructionsView mSplitInstructionsView;
 
@@ -182,6 +186,9 @@
 
     public void onDestroy() {
         mContext = null;
+        mActivityBackCallback = null;
+        mAppPairsController.onDestroy();
+        mSplitSelectDataHolder.onDestroy();
     }
 
     /**
@@ -289,7 +296,7 @@
      * To be called when the both split tasks are ready to be launched. Call after launcher side
      * animations are complete.
      */
-    public void launchSplitTasks(@SnapPosition int snapPosition,
+    public void launchSplitTasks(@PersistentSnapPosition int snapPosition,
             @Nullable Consumer<Boolean> callback) {
         Pair<InstanceId, com.android.launcher3.logging.InstanceId> instanceIds =
                 LogUtils.getShellShareableInstanceId();
@@ -302,6 +309,14 @@
     }
 
     /**
+     * A version of {@link #launchTasks(Consumer, boolean, int, InstanceId)} with no success
+     * callback.
+     */
+    public void launchSplitTasks(@PersistentSnapPosition int snapPosition) {
+        launchSplitTasks(snapPosition, /* callback */ null);
+    }
+
+    /**
      * A version of {@link #launchSplitTasks(int, Consumer)} that launches with default split ratio.
      */
     public void launchSplitTasks(@Nullable Consumer<Boolean> callback) {
@@ -350,7 +365,7 @@
      *                   foreground (quickswitch, launching previous pairs from overview)
      */
     public void launchTasks(@Nullable Consumer<Boolean> callback, boolean freezeTaskList,
-            @SnapPosition int snapPosition, @Nullable InstanceId shellInstanceId) {
+            @PersistentSnapPosition int snapPosition, @Nullable InstanceId shellInstanceId) {
         TestLogging.recordEvent(
                 TestProtocol.SEQUENCE_MAIN, "launchSplitTasks");
         final ActivityOptions options1 = ActivityOptions.makeBasic();
@@ -455,7 +470,8 @@
      */
     public void launchExistingSplitPair(@Nullable GroupedTaskView groupedTaskView,
             int firstTaskId, int secondTaskId, @StagePosition int stagePosition,
-            Consumer<Boolean> callback, boolean freezeTaskList, @SnapPosition int snapPosition) {
+            Consumer<Boolean> callback, boolean freezeTaskList,
+            @PersistentSnapPosition int snapPosition) {
         mLaunchingTaskView = groupedTaskView;
         final ActivityOptions options1 = ActivityOptions.makeBasic();
         if (freezeTaskList) {
@@ -590,13 +606,13 @@
 
         private final int mInitialTaskId;
         private final int mSecondTaskId;
-        private final Consumer<Boolean> mSuccessCallback;
+        private Consumer<Boolean> mFinishCallback;
 
         RemoteSplitLaunchTransitionRunner(int initialTaskId, int secondTaskId,
                 @Nullable Consumer<Boolean> callback) {
             mInitialTaskId = initialTaskId;
             mSecondTaskId = secondTaskId;
-            mSuccessCallback = callback;
+            mFinishCallback = callback;
         }
 
         @Override
@@ -615,10 +631,7 @@
                 TaskViewUtils.composeRecentsSplitLaunchAnimator(mLaunchingTaskView, mStateManager,
                         mDepthController, mInitialTaskId, mSecondTaskId, info, t, () -> {
                             finishAdapter.run();
-                            if (mSuccessCallback != null) {
-                                mSuccessCallback.accept(true);
-                            }
-                            resetState();
+                            cleanup(true /*success*/);
                         });
             });
         }
@@ -627,6 +640,27 @@
         public void mergeAnimation(IBinder transition, TransitionInfo info,
                 SurfaceControl.Transaction t, IBinder mergeTarget,
                 IRemoteTransitionFinishedCallback finishedCallback) { }
+
+        @Override
+        public void onTransitionConsumed(IBinder transition, boolean aborted)
+                throws RemoteException {
+            MAIN_EXECUTOR.execute(() -> {
+                cleanup(false /*success*/);
+            });
+        }
+
+        /**
+         * Must be called on UI thread.
+         * @param success if launching the split apps occurred successfully or not
+         */
+        @UiThread
+        private void cleanup(boolean success) {
+            if (mFinishCallback != null) {
+                mFinishCallback.accept(success);
+                mFinishCallback = null;
+            }
+            resetState();
+        }
     }
 
     /**
@@ -690,6 +724,7 @@
         mDismissingFromSplitPair = false;
         mFirstFloatingTaskView = null;
         mSplitInstructionsView = null;
+        mLaunchingFirstAppFullscreen = false;
     }
 
     /**
@@ -708,6 +743,10 @@
         return mSplitSelectDataHolder.isBothSplitAppsConfirmed();
     }
 
+    public boolean isLaunchingFirstAppFullscreen() {
+        return mLaunchingFirstAppFullscreen;
+    }
+
     public int getInitialTaskId() {
         return mSplitSelectDataHolder.getInitialTaskId();
     }
@@ -716,6 +755,9 @@
         return mSplitSelectDataHolder.getSecondTaskId();
     }
 
+    public void setLaunchingFirstAppFullscreen() {
+        mLaunchingFirstAppFullscreen = true;
+    }
     public void setFirstFloatingTaskView(FloatingTaskView floatingTaskView) {
         mFirstFloatingTaskView = floatingTaskView;
     }
@@ -773,7 +815,7 @@
                 @Override
                 public boolean onRequestSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
                         int splitPosition, Rect taskBounds) {
-                    if (!ENABLE_SPLIT_FROM_DESKTOP_TO_WORKSPACE.get()) return false;
+                    if (!DesktopTaskView.DESKTOP_MODE_SUPPORTED) return false;
                     MAIN_EXECUTOR.execute(() -> enterSplitSelect(taskInfo, splitPosition,
                             taskBounds));
                     return true;
diff --git a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
index 056f9aa..00b5621 100644
--- a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
@@ -16,9 +16,9 @@
 
 package com.android.quickstep.util;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_DESKTOP_TO_WORKSPACE;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_FULLSCREEN_WITH_KEYBOARD_SHORTCUTS;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -30,16 +30,21 @@
 import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.UserHandle;
 import android.view.View;
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.model.data.PackageItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.quickstep.views.DesktopTaskView;
 import com.android.quickstep.views.FloatingTaskView;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
@@ -52,12 +57,13 @@
     private final SplitSelectStateController mController;
 
     private final int mHalfDividerSize;
+    private final IconCache mIconCache;
 
     public SplitToWorkspaceController(Launcher launcher, SplitSelectStateController controller) {
         mLauncher = launcher;
         mDP = mLauncher.getDeviceProfile();
         mController = controller;
-
+        mIconCache = LauncherAppState.getInstanceNoCreate().getIconCache();
         mHalfDividerSize = mLauncher.getResources().getDimensionPixelSize(
                 R.dimen.multi_window_task_divider_size) / 2;
     }
@@ -74,17 +80,24 @@
             return false;
         }
 
-        // Convert original widgetView into bitmap to use for animation
-        // TODO(b/276361926) get the icon for this widget via PackageManager?
         int width = view.getWidth();
         int height = view.getHeight();
-        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
-        Canvas canvas = new Canvas(bitmap);
-        view.draw(canvas);
+        MODEL_EXECUTOR.execute(() -> {
+            PackageItemInfo infoInOut = new PackageItemInfo(pendingIntent.getCreatorPackage(),
+                    pendingIntent.getCreatorUserHandle());
+            mIconCache.getTitleAndIconForApp(infoInOut, false);
+            Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
 
-        mController.setSecondTask(pendingIntent);
+            view.post(() -> {
+                mController.setSecondTask(pendingIntent);
+                // Convert original widgetView into bitmap to use for animation
+                Canvas canvas = new Canvas(bitmap);
+                view.draw(canvas);
+                startWorkspaceAnimation(view, bitmap,
+                        new BitmapDrawable(mLauncher.getResources(), infoInOut.bitmap.icon));
+            });
+        });
 
-        startWorkspaceAnimation(view, bitmap, null /*icon*/);
         return true;
     }
 
@@ -180,7 +193,7 @@
     private boolean shouldIgnoreSecondSplitLaunch() {
         return (!ENABLE_SPLIT_FROM_FULLSCREEN_WITH_KEYBOARD_SHORTCUTS.get()
                 && !ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()
-                && !ENABLE_SPLIT_FROM_DESKTOP_TO_WORKSPACE.get())
+                && !DesktopTaskView.DESKTOP_MODE_SUPPORTED)
                 || !mController.isSplitSelectActive();
     }
 }
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 510044d..767aa15 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -384,6 +384,7 @@
         mInversePositionMatrix.mapRect(mTempRectF);
         mTempRectF.roundOut(mTmpCropRect);
 
+        params.setProgress(1f - fullScreenProgress);
         params.applySurfaceParams(params.createSurfaceParams(this));
 
         if (!DEBUG) {
diff --git a/quickstep/src/com/android/quickstep/util/TransformParams.java b/quickstep/src/com/android/quickstep/util/TransformParams.java
index 1cbded6..ca680db 100644
--- a/quickstep/src/com/android/quickstep/util/TransformParams.java
+++ b/quickstep/src/com/android/quickstep/util/TransformParams.java
@@ -15,8 +15,8 @@
  */
 package com.android.quickstep.util;
 
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 
 import android.util.FloatProperty;
 import android.view.RemoteAnimationTarget;
@@ -54,6 +54,7 @@
         }
     };
 
+    /** Progress from 0 to 1 where 0 is in-app and 1 is Overview */
     private float mProgress;
     private float mTargetAlpha;
     private float mCornerRadius;
@@ -135,6 +136,7 @@
         return this;
     }
 
+    /** Builds the SurfaceTransaction from the given BuilderProxy params. */
     public SurfaceTransaction createSurfaceParams(BuilderProxy proxy) {
         RemoteAnimationTargets targets = mTargetSet;
         SurfaceTransaction transaction = new SurfaceTransaction();
@@ -150,8 +152,12 @@
                 if (activityType == ACTIVITY_TYPE_HOME) {
                     mHomeBuilderProxy.onBuildTargetParams(builder, app, this);
                 } else {
-                    // Fade out Assistant overlay.
-                    if (activityType == ACTIVITY_TYPE_ASSISTANT && app.isNotInRecents) {
+                    // Fade out translucent overlay.
+                    // TODO(b/303351074): use app.isNotInRecents directly once it is fixed.
+                    boolean isNotInRecents = app.taskInfo != null
+                            && (app.taskInfo.baseIntent.getFlags()
+                                    & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0;
+                    if (app.isTranslucent && isNotInRecents) {
                         float progress = Utilities.boundToRange(getProgress(), 0, 1);
                         builder.setAlpha(1 - Interpolators.DECELERATE_QUINT
                                 .getInterpolation(progress));
diff --git a/quickstep/src/com/android/quickstep/views/ClearAllButton.java b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
index cfb4d0d..fba847f 100644
--- a/quickstep/src/com/android/quickstep/views/ClearAllButton.java
+++ b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
@@ -16,7 +16,7 @@
 
 package com.android.quickstep.views;
 
-import static com.android.launcher3.config.FeatureFlags.enableGridOnlyOverview;
+import static com.android.launcher3.Flags.enableGridOnlyOverview;
 
 import android.content.Context;
 import android.util.AttributeSet;
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
index dc6b5a2..9ff990e 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
@@ -18,7 +18,6 @@
 
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
-import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
 
 import android.content.Context;
@@ -41,7 +40,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.desktop.DesktopRecentsTransitionController;
@@ -338,16 +336,18 @@
     @Override
     public RunnableList launchTaskAnimated() {
         RunnableList endCallback = new RunnableList();
-        endCallback.add(() -> Launcher.getLauncher(mActivity).getStateManager().goToState(NORMAL));
 
+        RecentsView recentsView = getRecentsView();
         DesktopRecentsTransitionController recentsController =
-                getRecentsView().getDesktopRecentsController();
+                recentsView.getDesktopRecentsController();
         if (recentsController != null) {
             recentsController.launchDesktopFromRecents(this, success -> {
                 endCallback.executeAllAndDestroy();
             });
         }
 
+        // Callbacks get run from recentsView for case when recents animation already running
+        recentsView.addSideTaskLaunchCallback(endCallback);
         return endCallback;
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index 3d33c87..71758ad 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -35,7 +35,7 @@
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.recents.utilities.PreviewPositionHelper;
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
-import com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition;
+import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
 
 import java.util.HashMap;
 import java.util.function.Consumer;
@@ -200,9 +200,9 @@
     }
 
     /**
-     * Returns the {@link SnapPosition} of this pair of tasks.
+     * Returns the {@link PersistentSnapPosition} of this pair of tasks.
      */
-    public int getSnapPosition() {
+    public @PersistentSnapPosition int getSnapPosition() {
         if (mSplitBoundsConfig == null) {
             throw new IllegalStateException("mSplitBoundsConfig is null");
         }
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 1867fe9..fb9e640 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -137,16 +137,16 @@
     @Override
     public void onStateTransitionStart(LauncherState toState) {
         setOverviewStateEnabled(toState.overviewUi);
-        if (toState.overviewUi) {
-            // If overview is enabled, we want to update at the start
-            updateOverviewStateForDesktop(true);
-        }
+
         setOverviewGridEnabled(toState.displayOverviewTasksAsGrid(mActivity.getDeviceProfile()));
         setOverviewFullscreenEnabled(toState.getOverviewFullscreenProgress() == 1);
         if (toState == OVERVIEW_MODAL_TASK) {
             setOverviewSelectEnabled(true);
         }
         setFreezeViewVisibility(true);
+        if (mActivity.getDesktopVisibilityController() != null) {
+            mActivity.getDesktopVisibilityController().onLauncherStateChanged(toState);
+        }
     }
 
     @Override
@@ -167,11 +167,6 @@
             runActionOnRemoteHandles(remoteTargetHandle ->
                     remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(true));
         }
-
-        if (!finalState.overviewUi) {
-            // If overview is disabled, we want to update at the end
-            updateOverviewStateForDesktop(false);
-        }
     }
 
     @Override
@@ -183,9 +178,6 @@
                     & CLEAR_ALL_BUTTON) != 0;
             setDisallowScrollToClearAll(!hasClearAllButton);
         }
-        if (mActivity.getDesktopVisibilityController() != null) {
-            mActivity.getDesktopVisibilityController().setOverviewStateEnabled(enabled);
-        }
     }
 
     @Override
@@ -282,11 +274,4 @@
                     null /* transition */);
         }
     }
-
-    private void updateOverviewStateForDesktop(boolean enabled) {
-        DesktopVisibilityController controller = mActivity.getDesktopVisibilityController();
-        if (controller != null) {
-            controller.setOverviewStateEnabled(enabled);
-        }
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index 9141c99..7f1d619 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -29,9 +29,9 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Flags;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.R;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
 import com.android.launcher3.util.MultiValueAlpha;
@@ -289,7 +289,7 @@
             return 0;
         }
 
-        if (mDp.isTablet && FeatureFlags.enableGridOnlyOverview()) {
+        if (mDp.isTablet && Flags.enableGridOnlyOverview()) {
             return mDp.stashedTaskbarHeight;
         }
 
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 825c0ae..ef908c5 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -42,7 +42,7 @@
 import static com.android.launcher3.Utilities.mapToRange;
 import static com.android.launcher3.Utilities.squaredHypot;
 import static com.android.launcher3.Utilities.squaredTouchSlop;
-import static com.android.launcher3.config.FeatureFlags.enableGridOnlyOverview;
+import static com.android.launcher3.Flags.enableGridOnlyOverview;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_ACTIONS_SPLIT;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_CLEAR_ALL;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_DISMISS_SWIPE_UP;
@@ -217,6 +217,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
@@ -3263,7 +3264,10 @@
         mSplitSelectStateController.setFirstFloatingTaskView(firstFloatingTaskView);
 
         // Allow user to click staged app to launch into fullscreen
-        firstFloatingTaskView.setOnClickListener(this::animateToFullscreen);
+        firstFloatingTaskView.setOnClickListener(view ->
+                mSplitSelectStateController.getSplitAnimationController().
+                        playAnimPlaceholderToFullscreen(mActivity, view,
+                                Optional.of(() -> resetFromSplitSelectionState())));
 
         // SplitInstructionsView: animate in
         safeRemoveDragLayerView(mSplitSelectStateController.getSplitInstructionsView());
@@ -3316,41 +3320,6 @@
         });
     }
 
-    private void animateToFullscreen(View view) {
-        FloatingTaskView stagedTaskView = (FloatingTaskView) view;
-
-        boolean isTablet = mActivity.getDeviceProfile().isTablet;
-        int duration = isTablet
-                ? SplitAnimationTimings.TABLET_CONFIRM_DURATION
-                : SplitAnimationTimings.PHONE_CONFIRM_DURATION;
-
-        PendingAnimation pendingAnimation = new PendingAnimation(duration);
-
-        Rect firstTaskStartingBounds = new Rect();
-        Rect firstTaskEndingBounds = new Rect();
-
-        stagedTaskView.getBoundsOnScreen(firstTaskStartingBounds);
-        mActivity.getDragLayer().getBoundsOnScreen(firstTaskEndingBounds);
-
-        stagedTaskView.addConfirmAnimation(
-                pendingAnimation,
-                new RectF(firstTaskStartingBounds),
-                firstTaskEndingBounds,
-                false /* fadeWithThumbnail */,
-                true /* isStagedTask */);
-
-        pendingAnimation.addEndListener(animationSuccess ->
-                mSplitSelectStateController.launchInitialAppFullscreen(launchSuccess -> {
-                    if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
-                        mSplitSelectStateController.resetState();
-                    } else {
-                        resetFromSplitSelectionState();
-                    }
-                }));
-
-        pendingAnimation.buildAnim().start();
-    }
-
     /**
      * Creates a {@link PendingAnimation} for dismissing the specified {@link TaskView}.
      * @param dismissedTaskView the {@link TaskView} to be dismissed
@@ -4980,12 +4949,13 @@
 
     protected void maybeDrawEmptyMessage(Canvas canvas) {
         if (mShowEmptyMessage && mEmptyTextLayout != null) {
-            // Offset to center in the visible (non-padded) part of RecentsView
-            mTempRect.set(mInsets.left + getPaddingLeft(), mInsets.top + getPaddingTop(),
-                    mInsets.right + getPaddingRight(), mInsets.bottom + getPaddingBottom());
+            // Offsets icon and text up so that the vertical center of screen (accounting for
+            // insets) is between icon and text.
+            int offset = (mEmptyIcon.getIntrinsicHeight() + mEmptyMessagePadding) / 2;
+
             canvas.save();
-            canvas.translate(getScrollX() + (mTempRect.left - mTempRect.right) / 2,
-                    (mTempRect.top - mTempRect.bottom) / 2);
+            canvas.translate(getScrollX() + (mInsets.left - mInsets.right) / 2f,
+                    (mInsets.top - mInsets.bottom) / 2f - offset);
             mEmptyIcon.draw(canvas);
             canvas.translate(mEmptyMessagePadding,
                     mEmptyIcon.getBounds().bottom + mEmptyMessagePadding);
@@ -5975,6 +5945,13 @@
         dispatchScrollChanged();
     }
 
+    @Override
+    protected boolean shouldHandleRequestChildFocus() {
+        // If we are already scrolling to a task view, then the focus request has already been
+        // handled
+        return mScroller.isFinished();
+    }
+
     private void dispatchScrollChanged() {
         runActionOnRemoteHandles(remoteTargetHandle ->
                 remoteTargetHandle.getTaskViewSimulator().setScroll(getScrollOffset()));
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 6ae1973..dbdf058 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -77,6 +77,7 @@
 
 import com.android.app.animation.Interpolators;
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Flags;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
@@ -114,6 +115,8 @@
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
 
+import kotlin.Unit;
+
 import java.lang.annotation.Retention;
 import java.util.Arrays;
 import java.util.Collections;
@@ -122,8 +125,6 @@
 import java.util.function.Consumer;
 import java.util.stream.Stream;
 
-import kotlin.Unit;
-
 /**
  * A task in the Recents view.
  */
@@ -1021,6 +1022,9 @@
             anim.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationStart(Animator animation) {
+                    if (!recentsView.showAsGrid()) {
+                        return;
+                    }
                     recentsView.runActionOnRemoteHandles(
                             (Consumer<RemoteTargetHandle>) remoteTargetHandle ->
                                     remoteTargetHandle
@@ -1153,7 +1157,7 @@
         } else if (dp.isTablet) {
             int alignedOptionIndex = 0;
             if (getRecentsView().isOnGridBottomRow(menuContainer.getTaskView()) && dp.isLandscape) {
-                if (FeatureFlags.enableGridOnlyOverview()) {
+                if (Flags.enableGridOnlyOverview()) {
                     // With no focused task, there is less available space below the tasks, so align
                     // the arrow to the third option in the menu.
                     alignedOptionIndex = 2;
diff --git a/quickstep/tests/OWNERS b/quickstep/tests/OWNERS
index 02e8ebc..c271803 100644
--- a/quickstep/tests/OWNERS
+++ b/quickstep/tests/OWNERS
@@ -2,3 +2,4 @@
 sunnygoyal@google.com
 winsonc@google.com
 hyunyoungs@google.com
+mateuszc@google.com
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt
index 8c13fe3..f3115c6 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt
@@ -24,11 +24,11 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.Mock
-import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
 
 @RunWith(AndroidJUnit4::class)
 class FallbackTaskbarUIControllerTest : TaskbarBaseTestCase() {
@@ -36,8 +36,8 @@
     lateinit var fallbackTaskbarUIController: FallbackTaskbarUIController
     lateinit var stateListener: StateManager.StateListener<RecentsState>
 
-    @Mock lateinit var recentsActivity: RecentsActivity
-    @Mock lateinit var stateManager: StateManager<RecentsState>
+    private val recentsActivity: RecentsActivity = mock()
+    private val stateManager: StateManager<RecentsState> = mock()
 
     @Before
     override fun setup() {
@@ -46,10 +46,10 @@
         fallbackTaskbarUIController = FallbackTaskbarUIController(recentsActivity)
 
         // Capture registered state listener to send events to in our tests
-        val captor = ArgumentCaptor.forClass(StateManager.StateListener::class.java)
+        val captor = argumentCaptor<StateManager.StateListener<RecentsState>>()
         fallbackTaskbarUIController.init(taskbarControllers)
         verify(stateManager).addStateListener(captor.capture())
-        stateListener = captor.value as StateManager.StateListener<RecentsState>
+        stateListener = captor.lastValue
     }
 
     @Test
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt
index 148e36c..ed88c29 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt
@@ -23,17 +23,17 @@
 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED
 import org.junit.Before
 import org.junit.Test
-import org.mockito.Mock
-import org.mockito.Mockito.anyBoolean
-import org.mockito.Mockito.never
-import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
 
 class TaskbarKeyguardControllerTest : TaskbarBaseTestCase() {
 
-    @Mock lateinit var baseDragLayer: TaskbarDragLayer
-    @Mock lateinit var keyguardManager: KeyguardManager
+    private val baseDragLayer: TaskbarDragLayer = mock()
+    private val keyguardManager: KeyguardManager = mock()
 
     @Before
     override fun setup() {
@@ -50,7 +50,7 @@
     @Test
     fun uninterestingFlags_noActions() {
         setFlags(0)
-        verify(navbarButtonsViewController, never()).setKeyguardVisible(anyBoolean(), anyBoolean())
+        verify(navbarButtonsViewController, never()).setKeyguardVisible(any(), any())
     }
 
     @Test
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
index 16bfe70..37fcf43 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
@@ -19,32 +19,29 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.`when` as whenever
-import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
 
 @RunWith(AndroidJUnit4::class)
 class NavButtonLayoutFactoryTest {
 
-    @Mock lateinit var mockDeviceProfile: DeviceProfile
-    @Mock lateinit var mockParentButtonContainer: FrameLayout
-    @Mock lateinit var mockNavLayout: LinearLayout
-    @Mock lateinit var mockStartContextualLayout: ViewGroup
-    @Mock lateinit var mockEndContextualLayout: ViewGroup
-    @Mock lateinit var mockResources: Resources
-    @Mock lateinit var mockBackButton: ImageView
-    @Mock lateinit var mockRecentsButton: ImageView
-    @Mock lateinit var mockHomeButton: ImageView
-    @Mock lateinit var mockImeSwitcher: ImageView
-    @Mock lateinit var mockRotationButton: RotationButton
-    @Mock lateinit var mockA11yButton: ImageView
+    private val mockDeviceProfile: DeviceProfile = mock()
+    private val mockParentButtonContainer: FrameLayout = mock()
+    private val mockNavLayout: LinearLayout = mock()
+    private val mockStartContextualLayout: ViewGroup = mock()
+    private val mockEndContextualLayout: ViewGroup = mock()
+    private val mockResources: Resources = mock()
+    private val mockBackButton: ImageView = mock()
+    private val mockRecentsButton: ImageView = mock()
+    private val mockHomeButton: ImageView = mock()
+    private val mockImeSwitcher: ImageView = mock()
+    private val mockRotationButton: RotationButton = mock()
+    private val mockA11yButton: ImageView = mock()
 
     private var surfaceRotation = Surface.ROTATION_0
 
     @Before
     fun setup() {
-        MockitoAnnotations.initMocks(this)
-
         // Init end nav buttons
         whenever(mockNavLayout.childCount).thenReturn(3)
         whenever(mockNavLayout.findViewById<View>(R.id.back)).thenReturn(mockBackButton)
@@ -155,13 +152,13 @@
         mockDeviceProfile.isTaskbarPresent = false
         setDeviceProfileLandscape()
         val layoutter: NavButtonLayoutFactory.NavButtonLayoutter =
-                getLayoutter(
-                        isKidsMode = false,
-                        isInSetup = false,
-                        isThreeButtonNav = true,
-                        phoneMode = true,
-                        surfaceRotation = ROTATION_270
-                )
+            getLayoutter(
+                isKidsMode = false,
+                isInSetup = false,
+                isThreeButtonNav = true,
+                phoneMode = true,
+                surfaceRotation = ROTATION_270
+            )
         assert(layoutter is PhoneSeascapeNavLayoutter)
     }
 
diff --git a/quickstep/tests/src/com/android/quickstep/AbstractTaplTestsTaskbar.java b/quickstep/tests/src/com/android/quickstep/AbstractTaplTestsTaskbar.java
index 9c6c93d..ba9ae67 100644
--- a/quickstep/tests/src/com/android/quickstep/AbstractTaplTestsTaskbar.java
+++ b/quickstep/tests/src/com/android/quickstep/AbstractTaplTestsTaskbar.java
@@ -37,7 +37,6 @@
 
 public class AbstractTaplTestsTaskbar extends AbstractQuickStepTest {
 
-    protected static final String TEST_APP_NAME = "LauncherTestApp";
     protected static final String TEST_APP_PACKAGE =
             getInstrumentation().getContext().getPackageName();
     protected static final String CALCULATOR_APP_PACKAGE =
@@ -95,6 +94,6 @@
         launcher.enableTransientTaskbar(expectTransientTaskbar);
         launcher.recreateTaskbar();
         launcher.checkForAnomaly(true, true);
-        AbstractLauncherUiTest.checkDetectedLeaks(launcher);
+        AbstractLauncherUiTest.checkDetectedLeaks(launcher, true);
     }
 }
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index 99c79b3..16235e0 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -55,6 +55,7 @@
 import com.android.launcher3.tapl.OverviewTask;
 import com.android.launcher3.tapl.TestHelpers;
 import com.android.launcher3.testcomponent.TestCommandReceiver;
+import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.util.Wait;
 import com.android.launcher3.util.rule.FailureWatcher;
 import com.android.launcher3.util.rule.SamplerRule;
@@ -161,13 +162,14 @@
     @Before
     public void setUp() {
         mLauncher.onTestStart();
+        AbstractLauncherUiTest.verifyKeyguardInvisible();
     }
 
     @After
     public void tearDown() {
         try {
             // Limits UI tests affecting tests running after them.
-            AbstractQuickStepTest.checkDetectedLeaks(mLauncher);
+            AbstractQuickStepTest.checkDetectedLeaks(mLauncher, true);
         } finally {
             mLauncher.onTestFinish();
         }
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index eded1c9..a5d7724 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -185,7 +185,7 @@
                         + launcher.getNavigationModeMismatchError(false),
                 () -> launcher.getNavigationModeMismatchError(false) == null,
                 WAIT_TIME_MS, launcher);
-        AbstractLauncherUiTest.checkDetectedLeaks(launcher);
+        AbstractLauncherUiTest.checkDetectedLeaks(launcher, true);
         return true;
     }
 
diff --git a/quickstep/tests/src/com/android/quickstep/RecentsModelTest.java b/quickstep/tests/src/com/android/quickstep/RecentsModelTest.java
index 08e0898..c552d83 100644
--- a/quickstep/tests/src/com/android/quickstep/RecentsModelTest.java
+++ b/quickstep/tests/src/com/android/quickstep/RecentsModelTest.java
@@ -42,6 +42,7 @@
 import com.android.launcher3.icons.IconProvider;
 import com.android.quickstep.util.GroupTask;
 import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
 
 import org.junit.After;
 import org.junit.Before;
@@ -93,7 +94,7 @@
         when(mThumbnailCache.isPreloadingEnabled()).thenReturn(true);
 
         mRecentsModel = new RecentsModel(mContext, mTasksList, mock(TaskIconCache.class),
-                mThumbnailCache, mock(IconProvider.class));
+                mThumbnailCache, mock(IconProvider.class), mock(TaskStackChangeListeners.class));
 
         mResource = mock(Resources.class);
         when(mResource.getInteger((R.integer.recentsThumbnailCacheSize))).thenReturn(3);
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index f383949..689da48 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -64,7 +64,6 @@
 @RunWith(AndroidJUnit4.class)
 public class TaplTestsQuickstep extends AbstractQuickStepTest {
 
-    private static final String APP_NAME = "LauncherTestApp";
     private static final String CALCULATOR_APP_PACKAGE =
             resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR);
     private static final String READ_DEVICE_CONFIG_PERMISSION =
@@ -82,7 +81,7 @@
 
     @After
     public void tearDown() {
-        executeOnLauncher(launcher -> {
+        executeOnLauncherInTearDown(launcher -> {
             RecentsView recentsView = launcher.getOverviewPanel();
             recentsView.getPagedViewOrientedState().forceAllowRotationForTesting(false);
         });
@@ -212,6 +211,7 @@
 
 
     @Test
+    @ScreenRecord // b/303329286
     public void testOverviewActionsMenu_iconAppChipMenu() throws Exception {
         try (AutoCloseable c = TestUtil.overrideFlag(ENABLE_OVERVIEW_ICON_MENU, true)) {
             startTestAppsWithCheck();
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
index ed152f2..1e99f17 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
@@ -30,13 +30,13 @@
 import androidx.test.filters.LargeTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.tapl.OverviewTaskMenu;
 import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
 import com.android.launcher3.ui.TaplTestsLauncher3;
 import com.android.launcher3.util.TestUtil;
 import com.android.launcher3.util.rule.TestStabilityRule;
 import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch;
+import com.android.wm.shell.Flags;
 
 import org.junit.After;
 import org.junit.Before;
@@ -114,7 +114,7 @@
     @Ignore("Enable once App Pairs flagged on. These cause memory leaks b/297135374")
     public void testSaveAppPairMenuItemExistsOnSplitPair() throws Exception {
         assumeTrue("App pairs feature is currently not enabled, no test needed",
-                FeatureFlags.ENABLE_APP_PAIRS.get());
+                Flags.enableAppPairs());
 
         createAndLaunchASplitPair();
 
@@ -130,7 +130,7 @@
     @Ignore("Enable once App Pairs flagged on. These cause memory leaks b/297135374")
     public void testSaveAppPairMenuItemDoesNotExistOnSingleTask() throws Exception {
         assumeTrue("App pairs feature is currently not enabled, no test needed",
-                FeatureFlags.ENABLE_APP_PAIRS.get());
+                Flags.enableAppPairs());
 
         startAppFast(CALCULATOR_APP_PACKAGE);
 
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java b/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java
index 093c45d..0e382a4 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java
@@ -15,6 +15,7 @@
  */
 package com.android.quickstep;
 
+import static com.android.launcher3.util.TestConstants.AppNames.TEST_APP_NAME;
 import static com.android.quickstep.TaplTestsTaskbar.TaskbarMode.PERSISTENT;
 import static com.android.quickstep.TaplTestsTaskbar.TaskbarMode.TRANSIENT;
 
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsTransientTaskbar.java b/quickstep/tests/src/com/android/quickstep/TaplTestsTransientTaskbar.java
index b3cec4e..fc23a05 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsTransientTaskbar.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsTransientTaskbar.java
@@ -16,6 +16,7 @@
 package com.android.quickstep;
 
 import static com.android.launcher3.config.FeatureFlags.ENABLE_CURSOR_HOVER_STATES;
+import static com.android.launcher3.util.TestConstants.AppNames.TEST_APP_NAME;
 import static com.android.quickstep.TaskbarModeSwitchRule.Mode.TRANSIENT;
 
 import androidx.test.filters.LargeTest;
diff --git a/quickstep/tests/src/com/android/quickstep/TaskbarModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/TaskbarModeSwitchRule.java
index 9e41f74..e5657fb 100644
--- a/quickstep/tests/src/com/android/quickstep/TaskbarModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/TaskbarModeSwitchRule.java
@@ -123,7 +123,7 @@
         assertTrue(launcher, "Couldn't set taskbar=" + expectTransientTaskbar,
                 isTaskbarTransientMode(context) == expectTransientTaskbar, description);
 
-        AbstractLauncherUiTest.checkDetectedLeaks(launcher);
+        AbstractLauncherUiTest.checkDetectedLeaks(launcher, true);
     }
 
     private static void assertTrue(LauncherInstrumentation launcher, String message,
diff --git a/quickstep/tests/src/com/android/quickstep/util/AppPairsControllerTest.kt b/quickstep/tests/src/com/android/quickstep/util/AppPairsControllerTest.kt
new file mode 100644
index 0000000..1723844
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/util/AppPairsControllerTest.kt
@@ -0,0 +1,147 @@
+/*
+ * 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.content.Context
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.logging.StatsLogManager
+import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
+import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT
+import com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_30_70
+import com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50
+import com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_70_30
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidJUnit4::class)
+class AppPairsControllerTest {
+    @Mock lateinit var context: Context
+    @Mock lateinit var splitSelectStateController: SplitSelectStateController
+    @Mock lateinit var statsLogManager: StatsLogManager
+
+    private lateinit var appPairsController: AppPairsController
+
+    private val left30: Int by lazy {
+        appPairsController.encodeRank(STAGE_POSITION_TOP_OR_LEFT, SNAP_TO_30_70)
+    }
+    private val left50: Int by lazy {
+        appPairsController.encodeRank(STAGE_POSITION_TOP_OR_LEFT, SNAP_TO_50_50)
+    }
+    private val left70: Int by lazy {
+        appPairsController.encodeRank(STAGE_POSITION_TOP_OR_LEFT, SNAP_TO_70_30)
+    }
+    private val right30: Int by lazy {
+        appPairsController.encodeRank(STAGE_POSITION_BOTTOM_OR_RIGHT, SNAP_TO_30_70)
+    }
+    private val right50: Int by lazy {
+        appPairsController.encodeRank(STAGE_POSITION_BOTTOM_OR_RIGHT, SNAP_TO_50_50)
+    }
+    private val right70: Int by lazy {
+        appPairsController.encodeRank(STAGE_POSITION_BOTTOM_OR_RIGHT, SNAP_TO_70_30)
+    }
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        appPairsController =
+            AppPairsController(context, splitSelectStateController, statsLogManager)
+    }
+
+    @Test
+    fun shouldEncodeRankCorrectly() {
+        assertEquals("left + 30-70 should encode as 0 (0b0)", 0, left30)
+        assertEquals("left + 50-50 should encode as 1 (0b1)", 1, left50)
+        assertEquals("left + 70-30 should encode as 2 (0b10)", 2, left70)
+        // See AppPairsController#BITMASK_SIZE and BITMASK_FOR_SNAP_POSITION for context
+        assertEquals("right + 30-70 should encode as 1 followed by 16 0s", 1 shl 16, right30)
+        assertEquals("right + 50-50 should encode as the above value + 1", (1 shl 16) + 1, right50)
+        assertEquals("right + 70-30 should encode as the above value + 2", (1 shl 16) + 2, right70)
+    }
+
+    @Test
+    fun shouldDecodeRankCorrectly() {
+        assertEquals(
+            "left + 30-70 should decode to left",
+            STAGE_POSITION_TOP_OR_LEFT,
+            AppPairsController.convertRankToStagePosition(left30),
+        )
+        assertEquals(
+            "left + 30-70 should decode to 30-70",
+            SNAP_TO_30_70,
+            AppPairsController.convertRankToSnapPosition(left30),
+        )
+
+        assertEquals(
+            "left + 50-50 should decode to left",
+            STAGE_POSITION_TOP_OR_LEFT,
+            AppPairsController.convertRankToStagePosition(left50),
+        )
+        assertEquals(
+            "left + 50-50 should decode to 50-50",
+            SNAP_TO_50_50,
+            AppPairsController.convertRankToSnapPosition(left50),
+        )
+
+        assertEquals(
+            "left + 70-30 should decode to left",
+            STAGE_POSITION_TOP_OR_LEFT,
+            AppPairsController.convertRankToStagePosition(left70),
+        )
+        assertEquals(
+            "left + 70-30 should decode to 70-30",
+            SNAP_TO_70_30,
+            AppPairsController.convertRankToSnapPosition(left70),
+        )
+
+        assertEquals(
+            "right + 30-70 should decode to right",
+            STAGE_POSITION_BOTTOM_OR_RIGHT,
+            AppPairsController.convertRankToStagePosition(right30),
+        )
+        assertEquals(
+            "right + 30-70 should decode to 30-70",
+            SNAP_TO_30_70,
+            AppPairsController.convertRankToSnapPosition(right30),
+        )
+
+        assertEquals(
+            "right + 50-50 should decode to right",
+            STAGE_POSITION_BOTTOM_OR_RIGHT,
+            AppPairsController.convertRankToStagePosition(right50),
+        )
+        assertEquals(
+            "right + 50-50 should decode to 50-50",
+            SNAP_TO_50_50,
+            AppPairsController.convertRankToSnapPosition(right50),
+        )
+
+        assertEquals(
+            "right + 70-30 should decode to right",
+            STAGE_POSITION_BOTTOM_OR_RIGHT,
+            AppPairsController.convertRankToStagePosition(right70),
+        )
+        assertEquals(
+            "right + 70-30 should decode to 70-30",
+            SNAP_TO_70_30,
+            AppPairsController.convertRankToSnapPosition(right70),
+        )
+    }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt b/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
index 7e07b81..50803fe 100644
--- a/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
@@ -32,39 +32,36 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-import org.mockito.Mockito.`when` as whenever
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
 
 @RunWith(AndroidJUnit4::class)
 class SplitAnimationControllerTest {
 
     private val taskId = 9
 
-    @Mock lateinit var mockSplitSelectStateController: SplitSelectStateController
+    private val mockSplitSelectStateController: SplitSelectStateController = mock()
     // TaskView
-    @Mock lateinit var mockTaskView: TaskView
-    @Mock lateinit var mockThumbnailView: TaskThumbnailView
-    @Mock lateinit var mockBitmap: Bitmap
-    @Mock lateinit var mockIconView: IconView
-    @Mock lateinit var mockTaskViewDrawable: Drawable
+    private val mockTaskView: TaskView = mock()
+    private val mockThumbnailView: TaskThumbnailView = mock()
+    private val mockBitmap: Bitmap = mock()
+    private val mockIconView: IconView = mock()
+    private val mockTaskViewDrawable: Drawable = mock()
     // GroupedTaskView
-    @Mock lateinit var mockGroupedTaskView: GroupedTaskView
-    @Mock lateinit var mockTask: Task
-    @Mock lateinit var mockTaskKey: Task.TaskKey
-    @Mock lateinit var mockTaskIdAttributeContainer: TaskIdAttributeContainer
+    private val mockGroupedTaskView: GroupedTaskView = mock()
+    private val mockTask: Task = mock()
+    private val mockTaskKey: Task.TaskKey = mock()
+    private val mockTaskIdAttributeContainer: TaskIdAttributeContainer = mock()
 
     // SplitSelectSource
-    @Mock lateinit var splitSelectSource: SplitConfigurationOptions.SplitSelectSource
-    @Mock lateinit var mockSplitSourceDrawable: Drawable
-    @Mock lateinit var mockSplitSourceView: View
+    private val splitSelectSource: SplitConfigurationOptions.SplitSelectSource = mock()
+    private val mockSplitSourceDrawable: Drawable = mock()
+    private val mockSplitSourceView: View = mock()
 
     lateinit var splitAnimationController: SplitAnimationController
 
     @Before
     fun setup() {
-        MockitoAnnotations.initMocks(this)
-
         whenever(mockTaskView.thumbnail).thenReturn(mockThumbnailView)
         whenever(mockThumbnailView.thumbnail).thenReturn(mockBitmap)
         whenever(mockTaskView.iconView).thenReturn(mockIconView)
@@ -85,12 +82,14 @@
         // Missing taskView icon
         whenever(mockIconView.drawable).thenReturn(null)
 
-        val splitAnimInitProps : SplitAnimationController.Companion.SplitAnimInitProps =
-                splitAnimationController.getFirstAnimInitViews(
-                        { mockTaskView }, { splitSelectSource })
+        val splitAnimInitProps: SplitAnimationController.Companion.SplitAnimInitProps =
+            splitAnimationController.getFirstAnimInitViews({ mockTaskView }, { splitSelectSource })
 
-        assertEquals("Did not fallback to use splitSource icon drawable",
-                mockSplitSourceDrawable, splitAnimInitProps.iconDrawable)
+        assertEquals(
+            "Did not fallback to use splitSource icon drawable",
+            mockSplitSourceDrawable,
+            splitAnimInitProps.iconDrawable
+        )
     }
 
     @Test
@@ -99,12 +98,14 @@
         whenever(mockSplitSelectStateController.isAnimateCurrentTaskDismissal).thenReturn(true)
         whenever(mockSplitSelectStateController.isDismissingFromSplitPair).thenReturn(false)
 
-        val splitAnimInitProps : SplitAnimationController.Companion.SplitAnimInitProps =
-                splitAnimationController.getFirstAnimInitViews(
-                        { mockTaskView }, { splitSelectSource })
+        val splitAnimInitProps: SplitAnimationController.Companion.SplitAnimInitProps =
+            splitAnimationController.getFirstAnimInitViews({ mockTaskView }, { splitSelectSource })
 
-        assertEquals("Did not use taskView icon drawable", mockTaskViewDrawable,
-                splitAnimInitProps.iconDrawable)
+        assertEquals(
+            "Did not use taskView icon drawable",
+            mockTaskViewDrawable,
+            splitAnimInitProps.iconDrawable
+        )
     }
 
     @Test
@@ -116,12 +117,14 @@
         // Set split source to null
         whenever(splitSelectSource.drawable).thenReturn(null)
 
-        val splitAnimInitProps : SplitAnimationController.Companion.SplitAnimInitProps =
-                splitAnimationController.getFirstAnimInitViews(
-                        { mockTaskView }, { splitSelectSource })
+        val splitAnimInitProps: SplitAnimationController.Companion.SplitAnimInitProps =
+            splitAnimationController.getFirstAnimInitViews({ mockTaskView }, { splitSelectSource })
 
-        assertEquals("Did not use taskView icon drawable", mockTaskViewDrawable,
-                splitAnimInitProps.iconDrawable)
+        assertEquals(
+            "Did not use taskView icon drawable",
+            mockTaskViewDrawable,
+            splitAnimInitProps.iconDrawable
+        )
     }
 
     @Test
@@ -130,12 +133,14 @@
         whenever(mockSplitSelectStateController.isAnimateCurrentTaskDismissal).thenReturn(false)
         whenever(mockSplitSelectStateController.isDismissingFromSplitPair).thenReturn(false)
 
-        val splitAnimInitProps : SplitAnimationController.Companion.SplitAnimInitProps =
-                splitAnimationController.getFirstAnimInitViews(
-                        { mockTaskView }, { splitSelectSource })
+        val splitAnimInitProps: SplitAnimationController.Companion.SplitAnimInitProps =
+            splitAnimationController.getFirstAnimInitViews({ mockTaskView }, { splitSelectSource })
 
-        assertEquals("Did not use splitSource icon drawable", mockSplitSourceDrawable,
-                splitAnimInitProps.iconDrawable)
+        assertEquals(
+            "Did not use splitSource icon drawable",
+            mockSplitSourceDrawable,
+            splitAnimInitProps.iconDrawable
+        )
     }
 
     @Test
@@ -154,12 +159,17 @@
         whenever(mockTaskKey.getId()).thenReturn(taskId)
         whenever(mockSplitSelectStateController.initialTaskId).thenReturn(taskId)
         whenever(mockGroupedTaskView.taskIdAttributeContainers)
-                .thenReturn(Array(1) { mockTaskIdAttributeContainer })
-        val splitAnimInitProps : SplitAnimationController.Companion.SplitAnimInitProps =
-                splitAnimationController.getFirstAnimInitViews(
-                        { mockGroupedTaskView }, { splitSelectSource })
+            .thenReturn(Array(1) { mockTaskIdAttributeContainer })
+        val splitAnimInitProps: SplitAnimationController.Companion.SplitAnimInitProps =
+            splitAnimationController.getFirstAnimInitViews(
+                { mockGroupedTaskView },
+                { splitSelectSource }
+            )
 
-        assertEquals("Did not use splitSource icon drawable", mockSplitSourceDrawable,
-                splitAnimInitProps.iconDrawable)
+        assertEquals(
+            "Did not use splitSource icon drawable",
+            mockSplitSourceDrawable,
+            splitAnimInitProps.iconDrawable
+        )
     }
-}
\ No newline at end of file
+}
diff --git a/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt b/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
index f198741..f292f9a 100644
--- a/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
@@ -33,11 +33,11 @@
 import com.android.launcher3.statemanager.StatefulActivity
 import com.android.launcher3.util.ComponentKey
 import com.android.launcher3.util.SplitConfigurationOptions
-import com.android.launcher3.util.withArgCaptor
 import com.android.quickstep.RecentsModel
 import com.android.quickstep.SystemUiProxy
 import com.android.systemui.shared.recents.model.Task
 import com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50
+import java.util.function.Consumer
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertNull
@@ -45,23 +45,22 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
-import org.mockito.MockitoAnnotations
-import java.util.function.Consumer
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
 
 @RunWith(AndroidJUnit4::class)
 class SplitSelectStateControllerTest {
 
-    @Mock lateinit var systemUiProxy: SystemUiProxy
-    @Mock lateinit var depthController: DepthController
-    @Mock lateinit var statsLogManager: StatsLogManager
-    @Mock lateinit var stateManager: StateManager<LauncherState>
-    @Mock lateinit var handler: Handler
-    @Mock lateinit var context: StatefulActivity<*>
-    @Mock lateinit var recentsModel: RecentsModel
-    @Mock lateinit var pendingIntent: PendingIntent
+    private val systemUiProxy: SystemUiProxy = mock()
+    private val depthController: DepthController = mock()
+    private val statsLogManager: StatsLogManager = mock()
+    private val stateManager: StateManager<LauncherState> = mock()
+    private val handler: Handler = mock()
+    private val context: StatefulActivity<*> = mock()
+    private val recentsModel: RecentsModel = mock()
+    private val pendingIntent: PendingIntent = mock()
 
     lateinit var splitSelectStateController: SplitSelectStateController
 
@@ -69,11 +68,12 @@
     private val nonPrimaryUserHandle = UserHandle(ActivityManager.RunningTaskInfo().userId + 10)
 
     private var taskIdCounter = 0
-    private fun getUniqueId(): Int { return ++taskIdCounter }
+    private fun getUniqueId(): Int {
+        return ++taskIdCounter
+    }
 
     @Before
     fun setup() {
-        MockitoAnnotations.initMocks(this)
         splitSelectStateController =
             SplitSelectStateController(
                 context,
@@ -111,13 +111,14 @@
 
         // Capture callback from recentsModel#getTasks()
         val consumer =
-            withArgCaptor<Consumer<ArrayList<GroupTask>>> {
-                splitSelectStateController.findLastActiveTasksAndRunCallback(
-                    listOf(nonMatchingComponent),
-                    taskConsumer
-                )
-                verify(recentsModel).getTasks(capture())
-            }
+            argumentCaptor<Consumer<ArrayList<GroupTask>>> {
+                    splitSelectStateController.findLastActiveTasksAndRunCallback(
+                        listOf(nonMatchingComponent),
+                        taskConsumer
+                    )
+                    verify(recentsModel).getTasks(capture())
+                }
+                .lastValue
 
         // Send our mocked tasks
         consumer.accept(tasks)
@@ -162,13 +163,14 @@
 
         // Capture callback from recentsModel#getTasks()
         val consumer =
-            withArgCaptor<Consumer<ArrayList<GroupTask>>> {
-                splitSelectStateController.findLastActiveTasksAndRunCallback(
-                    listOf(matchingComponent),
-                    taskConsumer
-                )
-                verify(recentsModel).getTasks(capture())
-            }
+            argumentCaptor<Consumer<ArrayList<GroupTask>>> {
+                    splitSelectStateController.findLastActiveTasksAndRunCallback(
+                        listOf(matchingComponent),
+                        taskConsumer
+                    )
+                    verify(recentsModel).getTasks(capture())
+                }
+                .lastValue
 
         // Send our mocked tasks
         consumer.accept(tasks)
@@ -201,13 +203,14 @@
 
         // Capture callback from recentsModel#getTasks()
         val consumer =
-            withArgCaptor<Consumer<ArrayList<GroupTask>>> {
-                splitSelectStateController.findLastActiveTasksAndRunCallback(
-                    listOf(nonPrimaryUserComponent),
-                    taskConsumer
-                )
-                verify(recentsModel).getTasks(capture())
-            }
+            argumentCaptor<Consumer<ArrayList<GroupTask>>> {
+                    splitSelectStateController.findLastActiveTasksAndRunCallback(
+                        listOf(nonPrimaryUserComponent),
+                        taskConsumer
+                    )
+                    verify(recentsModel).getTasks(capture())
+                }
+                .lastValue
 
         // Send our mocked tasks
         consumer.accept(tasks)
@@ -255,13 +258,14 @@
 
         // Capture callback from recentsModel#getTasks()
         val consumer =
-            withArgCaptor<Consumer<ArrayList<GroupTask>>> {
-                splitSelectStateController.findLastActiveTasksAndRunCallback(
-                    listOf(nonPrimaryUserComponent),
-                    taskConsumer
-                )
-                verify(recentsModel).getTasks(capture())
-            }
+            argumentCaptor<Consumer<ArrayList<GroupTask>>> {
+                    splitSelectStateController.findLastActiveTasksAndRunCallback(
+                        listOf(nonPrimaryUserComponent),
+                        taskConsumer
+                    )
+                    verify(recentsModel).getTasks(capture())
+                }
+                .lastValue
 
         // Send our mocked tasks
         consumer.accept(tasks)
@@ -306,13 +310,14 @@
 
         // Capture callback from recentsModel#getTasks()
         val consumer =
-            withArgCaptor<Consumer<ArrayList<GroupTask>>> {
-                splitSelectStateController.findLastActiveTasksAndRunCallback(
-                    listOf(matchingComponent),
-                    taskConsumer
-                )
-                verify(recentsModel).getTasks(capture())
-            }
+            argumentCaptor<Consumer<ArrayList<GroupTask>>> {
+                    splitSelectStateController.findLastActiveTasksAndRunCallback(
+                        listOf(matchingComponent),
+                        taskConsumer
+                    )
+                    verify(recentsModel).getTasks(capture())
+                }
+                .lastValue
 
         // Send our mocked tasks
         consumer.accept(tasks)
@@ -327,10 +332,7 @@
             ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle)
 
         val groupTask1 =
-            generateGroupTask(
-                ComponentName("hotdog", "pie"),
-                ComponentName("pumpkin", "pie")
-            )
+            generateGroupTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie"))
         val groupTask2 =
             generateGroupTask(
                 ComponentName("pomegranate", "juice"),
@@ -361,13 +363,14 @@
 
         // Capture callback from recentsModel#getTasks()
         val consumer =
-            withArgCaptor<Consumer<ArrayList<GroupTask>>> {
-                splitSelectStateController.findLastActiveTasksAndRunCallback(
-                    listOf(nonMatchingComponent, matchingComponent),
-                    taskConsumer
-                )
-                verify(recentsModel).getTasks(capture())
-            }
+            argumentCaptor<Consumer<ArrayList<GroupTask>>> {
+                    splitSelectStateController.findLastActiveTasksAndRunCallback(
+                        listOf(nonMatchingComponent, matchingComponent),
+                        taskConsumer
+                    )
+                    verify(recentsModel).getTasks(capture())
+                }
+                .lastValue
 
         // Send our mocked tasks
         consumer.accept(tasks)
@@ -381,10 +384,7 @@
             ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle)
 
         val groupTask1 =
-            generateGroupTask(
-                ComponentName("hotdog", "pie"),
-                ComponentName("pumpkin", "pie")
-            )
+            generateGroupTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie"))
         val groupTask2 =
             generateGroupTask(
                 ComponentName("pomegranate", "juice"),
@@ -415,13 +415,14 @@
 
         // Capture callback from recentsModel#getTasks()
         val consumer =
-            withArgCaptor<Consumer<ArrayList<GroupTask>>> {
-                splitSelectStateController.findLastActiveTasksAndRunCallback(
-                    listOf(matchingComponent, matchingComponent),
-                    taskConsumer
-                )
-                verify(recentsModel).getTasks(capture())
-            }
+            argumentCaptor<Consumer<ArrayList<GroupTask>>> {
+                    splitSelectStateController.findLastActiveTasksAndRunCallback(
+                        listOf(matchingComponent, matchingComponent),
+                        taskConsumer
+                    )
+                    verify(recentsModel).getTasks(capture())
+                }
+                .lastValue
 
         // Send our mocked tasks
         consumer.accept(tasks)
@@ -479,13 +480,14 @@
 
         // Capture callback from recentsModel#getTasks()
         val consumer =
-            withArgCaptor<Consumer<ArrayList<GroupTask>>> {
-                splitSelectStateController.findLastActiveTasksAndRunCallback(
-                    listOf(matchingComponent, matchingComponent),
-                    taskConsumer
-                )
-                verify(recentsModel).getTasks(capture())
-            }
+            argumentCaptor<Consumer<ArrayList<GroupTask>>> {
+                    splitSelectStateController.findLastActiveTasksAndRunCallback(
+                        listOf(matchingComponent, matchingComponent),
+                        taskConsumer
+                    )
+                    verify(recentsModel).getTasks(capture())
+                }
+                .lastValue
 
         // Send our mocked tasks
         consumer.accept(tasks)
@@ -531,7 +533,7 @@
     @Test
     fun secondPendingIntentSet() {
         val itemInfo = ItemInfo()
-        `when`(pendingIntent.creatorUserHandle).thenReturn(primaryUserHandle)
+        whenever(pendingIntent.creatorUserHandle).thenReturn(primaryUserHandle)
         splitSelectStateController.setInitialTaskSelect(null, 0, itemInfo, null, 1)
         splitSelectStateController.setSecondTask(pendingIntent)
         assertTrue(splitSelectStateController.isBothSplitAppsConfirmed)
diff --git a/quickstep/tests/src/com/android/quickstep/util/unfold/PreemptiveUnfoldTransitionProgressProviderTest.kt b/quickstep/tests/src/com/android/quickstep/util/unfold/PreemptiveUnfoldTransitionProgressProviderTest.kt
index f73be72..6a418a4 100644
--- a/quickstep/tests/src/com/android/quickstep/util/unfold/PreemptiveUnfoldTransitionProgressProviderTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/util/unfold/PreemptiveUnfoldTransitionProgressProviderTest.kt
@@ -21,19 +21,17 @@
 import android.testing.TestableLooper.RunWithLooper
 import android.util.Log
 import androidx.test.filters.SmallTest
-import com.android.launcher3.util.any
-import com.android.launcher3.util.mock
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mockito.anyBoolean
-import org.mockito.Mockito.anyFloat
-import org.mockito.Mockito.inOrder
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.inOrder
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -74,7 +72,7 @@
         provider.preemptivelyStartTransition(initialProgress = null)
 
         verify(listener).onTransitionStarted()
-        verify(listener, never()).onTransitionProgress(anyFloat())
+        verify(listener, never()).onTransitionProgress(any())
     }
 
     @Test
@@ -90,7 +88,7 @@
         provider.preemptivelyStartTransition()
         provider.cancelPreemptiveStart()
 
-        with(inOrder(listener)) {
+        inOrder(listener) {
             verify(listener).onTransitionStarted()
             verify(listener).onTransitionFinished()
         }
@@ -111,7 +109,7 @@
         source.onTransitionStarted()
         source.onTransitionFinished()
 
-        with(inOrder(listener)) {
+        inOrder(listener) {
             verify(listener).onTransitionStarted()
             verify(listener).onTransitionFinished()
         }
@@ -152,7 +150,7 @@
         provider.preemptivelyStartTransition()
         source.onTransitionFinished()
 
-        with(inOrder(listener)) {
+        inOrder(listener) {
             verify(listener).onTransitionStarted()
             verify(listener).onTransitionFinished()
         }
@@ -165,7 +163,7 @@
         testableLooper.moveTimeForward(PREEMPTIVE_UNFOLD_TIMEOUT_MS + 1)
         testableLooper.processAllMessages()
 
-        with(inOrder(listener)) {
+        inOrder(listener) {
             verify(listener).onTransitionStarted()
             verify(listener).onTransitionFinished()
         }
@@ -178,7 +176,7 @@
         testableLooper.moveTimeForward(PREEMPTIVE_UNFOLD_TIMEOUT_MS + 1)
         testableLooper.processAllMessages()
 
-        verify(testWtfHandler).onTerribleFailure(any(), any(), anyBoolean())
+        verify(testWtfHandler).onTerribleFailure(any(), any(), any())
     }
 
     @Test
@@ -225,7 +223,7 @@
 
         source.onTransitionFinished()
 
-        with(inOrder(listener)) {
+        inOrder(listener) {
             verify(listener).onTransitionStarted()
             verify(listener).onTransitionFinished()
         }
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index aa96397..53c9c04 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Verdeelde skerm"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Programinligting vir %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Stoor apppaar"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Raak en hou om \'n legstuk te skuif."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Dubbeltik en hou om \'n legstuk te skuif of gebruik gepasmaakte handelinge."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index bcc626e..ce93db0 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"የተከፈለ ማያ ገፅ"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"የመተግበሪያ መረጃ ለ%1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"የመተግበሪያ ጥምረትን ያስቀምጡ"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"ምግብርን ለማንቀሳቀስ ይንኩ እና ይያዙ።"</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"ምግብርን ለማንቀሳቀስ ወይም ብጁ እርምጃዎችን ለመጠቀም ሁለቴ መታ ያድርጉ እና ይያዙ።"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 69e96cb..fa41e5a 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"تقسيم الشاشة"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"‏معلومات تطبيق %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"حفظ إعدادات ميزة \"استخدام تطبيقين في الوقت نفسه\""</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"انقر مع الاستمرار لنقل أداة."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"انقر مرتين مع تثبيت إصبعك لنقل أداة أو استخدام الإجراءات المخصّصة."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index 8c18e8d..3dcfc0f 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"বিভাজিত স্ক্ৰীন"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$sৰ বাবে এপৰ তথ্য"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"এপৰ পেয়াৰ ছেভ কৰক"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"ৱিজেট স্থানান্তৰ কৰিবলৈ টিপি ধৰি ৰাখক।"</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"কোনো ৱিজেট স্থানান্তৰ কৰিবলৈ দুবাৰ টিপি ধৰি ৰাখক অথবা কাষ্টম কাৰ্য ব্যৱহাৰ কৰক।"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index bf12761..eb8162d 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Ekran bölünməsi"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s ilə bağlı tətbiq məlumatı"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Tətbiq cütünü saxlayın"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Vidceti daşımaq üçün toxunub saxlayın."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Vidceti daşımaq üçün iki dəfə toxunub saxlayın və ya fərdi əməliyyatlardan istifadə edin."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index cfb6fbd..2b2b9ca 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Podeljeni ekran"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Informacije o aplikaciji za: %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Sačuvaj par aplikacija"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Dodirnite i zadržite radi pomeranja vidžeta."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Dvaput dodirnite i zadržite da biste pomerali vidžet ili koristite prilagođene radnje."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d×%2$d"</string>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index 5950ec5..9dc4d26 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Падзелены экран"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Інфармацыя пра праграму для: %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Захаваць спалучэнне праграм"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Націсніце і ўтрымлівайце віджэт для перамяшчэння."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Дакраніцеся двойчы і ўтрымлівайце, каб перамясціць віджэт або выкарыстоўваць спецыяльныя дзеянні."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index b0f1c4c..a6b770c 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Разделен екран"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Информация за приложението за %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Запазване на двойката приложения"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Докоснете и задръжте за преместване на приспособление"</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Докоснете двукратно и задръжте за преместване на приспособление или използвайте персонал. действия."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index e28638b..c14dd63 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"স্প্লিট স্ক্রিন"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s-এর জন্য অ্যাপ সম্পর্কিত তথ্য"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"অ্যাপ পেয়ার সেভ করুন"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"কোনও উইজেট সরাতে সেটি টাচ করে ধরে রাখুন।"</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"একটি উইজেট সরাতে বা কাস্টম অ্যাকশন ব্যবহার করতে ডবল ট্যাপ করে ধরে রাখুন।"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 8387f14..41663ba 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Podijeljeni ekran"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Informacije o aplikaciji %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Sačuvaj par aplikacija"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Dodirnite i zadržite da pomjerite vidžet."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Dvaput dodirnite i zadržite da pomjerite vidžet ili da koristite prilagođene radnje."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index dd2d03f..0e92a77 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Pantalla dividida"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Informació de l\'aplicació %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Desa la parella d\'aplicacions"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Fes doble toc i mantén premut per moure un widget."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Fes doble toc i mantén premut per moure un widget o per utilitzar accions personalitzades."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index f9c7c77..a53986d 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Rozdělit obrazovku"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Informace o aplikaci %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Uložit pár aplikací"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Widget přesunete klepnutím a podržením."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Dvojitým klepnutím a podržením přesunete widget, případně použijte vlastní akce."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 134cbb0..28cc46c 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Opdel skærm"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Appinfo for %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Gem appsammenknytning"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Hold en widget nede for at flytte den."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Tryk to gange, og hold en widget nede for at flytte den eller bruge tilpassede handlinger."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 2fa7f91..025fd48 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Splitscreen"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"App-Info für %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"App-Paar speichern"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Zum Verschieben des Widgets berühren und halten"</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Doppeltippen und halten, um ein Widget zu bewegen oder benutzerdefinierte Aktionen zu nutzen."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 35cac5b..3e8c6f9 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Διαχωρισμός οθόνης"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Πληροφορίες εφαρμογής για %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Αποθήκευση ζεύγους εφαρμογών"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Πατήστε παρατετ. για μετακίνηση γραφ. στοιχείου."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Πατήστε δύο φορές παρατεταμένα για μετακίνηση γραφικού στοιχείου ή χρήση προσαρμοσμένων ενεργειών."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index 47a67a8..14bf203 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Split screen"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"App info for %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Save app pair"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Touch and hold to move a widget."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Double-tap &amp; hold to move a widget or use custom actions."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index cc86e87..4b28659 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -30,6 +30,7 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Split screen"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"App info for %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Save app pair"</string>
+    <string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Touch and hold to move a widget."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Double-tap and hold to move a widget or use custom actions."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 47a67a8..14bf203 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Split screen"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"App info for %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Save app pair"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Touch and hold to move a widget."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Double-tap &amp; hold to move a widget or use custom actions."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index 47a67a8..14bf203 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Split screen"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"App info for %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Save app pair"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Touch and hold to move a widget."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Double-tap &amp; hold to move a widget or use custom actions."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
index cca6551..7f1c8ec 100644
--- a/res/values-en-rXC/strings.xml
+++ b/res/values-en-rXC/strings.xml
@@ -30,6 +30,7 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‏‏‎‎‏‎‏‎‎‏‎‏‏‎‎‏‏‏‏‏‏‎‎‎‎‏‏‎‏‏‎‎‏‏‎‎‎‏‏‎‏‎‏‎‏‎‎‏‏‏‏‏‏‎Split screen‎‏‎‎‏‎"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‎‎‎‏‏‏‎‏‏‏‏‏‏‎‎‏‏‎‏‏‎‎‏‏‏‎‏‎‏‎‏‎‎‎‎‏‎‎‎‏‎‏‎‎‎App info for %1$s‎‏‎‎‏‎"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‏‏‎‎‎‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‎‏‏‏‏‎‎‏‏‎‎‎‎‏‏‏‎‎‎‎‎‎‏‏‎‎‎‎‏‎‎‎‏‏‎Save app pair‎‏‎‎‏‎"</string>
+    <string name="app_pair_default_title" msgid="4045241727446873529">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‎‎‎‎‏‎‎‎‏‏‏‎‎‏‎‏‎‏‏‏‏‎‏‎‏‏‎‎‏‎‎‎‏‏‎‎‎‎‏‏‏‎‏‏‏‏‎‏‎‏‏‎‏‏‏‎‎‏‎‎‏‎‎‏‏‎<xliff:g id="APP1">%1$s</xliff:g>‎‏‎‎‏‏‏‎ | ‎‏‎‎‏‏‎<xliff:g id="APP2">%2$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‎‎‎‏‏‏‎‎‏‎‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‏‎‎‏‏‎‎‎‏‎‎‎‎‏‎‏‎‎‏‎‎‎‏‏‎‎‎‎‎‏‎‏‎‎‏‎Touch &amp; hold to move a widget.‎‏‎‎‏‎"</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‎‏‎‏‏‏‏‎‏‏‏‏‏‎‏‎‏‎‎‎‎‎‎‎‏‏‏‏‎‎‎‎‏‏‎‏‎‏‏‎‎‎‎‎‎‏‎‏‎‎‎‎‎‎‎‎‎‏‏‎‎Double-tap &amp; hold to move a widget or use custom actions.‎‏‎‎‏‎"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‎‎‎‎‏‏‏‎‎‏‏‎‏‎‎‏‏‏‏‏‏‎‎‏‏‎‎‏‏‎‏‎‎‎‎‎‎‏‎‏‎‎‎‏‎‎‎‎‎‏‎‎‎‏‎‏‏‏‏‏‎%1$d × %2$d‎‏‎‎‏‎"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index c49fa0e..3efa461 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Pantalla dividida"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Información de la app de %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Guardar vinculación de apps"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Mantén presionado para mover un widget."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Presiona dos veces y mantén presionado para mover un widget o usar acciones personalizadas."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index b89a717..4a857a6 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Pantalla dividida"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Información de la aplicación %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Guardar aplicaciones emparejadas"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Mantén pulsado un widget para moverlo"</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Toca dos veces y mantén pulsado un widget para moverlo o usar acciones personalizadas."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 333fded..67a6b3a 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Jagatud ekraanikuva"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Rakenduse teave: %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Salvesta rakendusepaar"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Vidina teisaldamiseks puudutage ja hoidke all."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Vidina teisaldamiseks või kohandatud toimingute kasutamiseks topeltpuudutage ja hoidke all."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index cbf7e1c..78f66ef 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Zatitu pantaila"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s aplikazioari buruzko informazioa"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Gorde aplikazio parea"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Eduki sakatuta widget bat mugitzeko."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Sakatu birritan eta eduki sakatuta widget bat mugitzeko edo ekintza pertsonalizatuak erabiltzeko."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 4ec760e..05fea06 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"صفحهٔ دونیمه"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"‏اطلاعات برنامه %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"ذخیره جفت برنامه"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"برای جابه‌جا کردن ابزارک، لمس کنید و نگه دارید."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"برای جابه‌جا کردن ابزارک یا استفاده از کنش‌های سفارشی، دوضربه بزنید و نگه دارید."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 16dabb7..2c68f5e 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Jaettu näyttö"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Sovellustiedot: %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Tallenna sovelluspari"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Kosketa pitkään, niin voit siirtää widgetiä."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Kaksoisnapauta ja paina pitkään, niin voit siirtää widgetiä tai käyttää muokattuja toimintoja."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index 1267205..3bd3010 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Écran partagé"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Renseignements sur l\'appli pour %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Enregistrer la paire d\'applications"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Maintenez le doigt sur un widget pour le déplacer."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Touchez 2x un widget et maintenez le doigt dessus pour le déplacer ou utiliser des actions personnalisées."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 1addca7..e80b78c 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Écran partagé"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Infos sur l\'appli pour %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Enregistrer la paire d\'applis"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Appuyez de manière prolongée sur un widget pour le déplacer."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Appuyez deux fois et maintenez la pression pour déplacer widget ou utiliser actions personnalisées."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d x %2$d"</string>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index cf6ac1c..c670d2a 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Pantalla dividida"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Información da aplicación para %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Gardar emparellamento de aplicacións"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Mantén premido un widget para movelo."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Toca dúas veces un widget e manteno premido para movelo ou utiliza accións personalizadas."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 7f5c39f..e0b4be0 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"સ્ક્રીનને વિભાજિત કરો"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s માટે ઍપ માહિતી"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"ઍપની જોડી સાચવો"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"વિજેટ ખસેડવા ટચ કરીને થોડી વાર દબાવી રાખો."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"વિજેટ ખસેડવા બે વાર ટૅપ કરીને દબાવી રાખો અથવા કસ્ટમ ક્રિયાઓનો ઉપયોગ કરો."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 8301194..382d0bb 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"स्प्लिट स्क्रीन"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s के लिए ऐप्लिकेशन की जानकारी"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"साथ में इस्तेमाल किए जा सकने वाले ऐप्लिकेशन की जानकारी सेव करें"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"किसी विजेट को एक से दूसरी जगह ले जाने के लिए, उसे दबाकर रखें."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"किसी विजेट को एक से दूसरी जगह ले जाने के लिए, उस पर दो बार टैप करके दबाकर रखें या पसंद के मुताबिक कार्रवाइयां इस्तेमाल करें."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 59630f4..4d144ac 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Podijeljeni zaslon"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Informacije o aplikaciji %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Spremi par aplikacija"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Dodirnite i zadržite da biste premjestili widget."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Dvaput dodirnite i zadržite pritisak da biste premjestili widget ili upotrijebite prilagođene radnje"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 3bcf350..58a9c60 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Osztott képernyő"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Alkalmazásinformáció a következőhöz: %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Alkalmazáspár mentése"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Tartsa lenyomva a modult az áthelyezéshez."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Modul áthelyezéséhez koppintson duplán, tartsa nyomva az ujját, vagy használjon egyéni műveleteket."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index ccc028f..b0344b1 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Տրոհել էկրանը"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Տեղեկություններ %1$s հավելվածի մասին"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Պահել հավելվածների զույգը"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Հպեք և պահեք՝ վիջեթ տեղափոխելու համար։"</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Կրկնակի հպեք և պահեք՝ վիջեթ տեղափոխելու համար, կամ օգտվեք հատուկ գործողություններից։"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index c1d62d4..c78441c 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Layar terpisah"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Info aplikasi untuk %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Simpan pasangan aplikasi"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Sentuh lama untuk memindahkan widget."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Ketuk dua kali &amp; tahan untuk memindahkan widget atau gunakan tindakan khusus."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index e336a4b..af29aee 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Skipta skjá"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Upplýsingar um forrit fyrir %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Vista forritapar"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Haltu fingri á græju til að færa hana."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Ýttu tvisvar og haltu fingri á græju til að færa hana eða notaðu sérsniðnar aðgerðir."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 4342ce6..865a1bc 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Schermo diviso"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Informazioni sull\'app %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Salva coppia di app"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Tocca e tieni premuto per spostare un widget."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Tocca due volte e tieni premuto per spostare un widget o per usare le azioni personalizzate."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 808d324..94dbd54 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"מסך מפוצל"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"‏פרטים על האפליקציה %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"שמירה של צמד אפליקציות"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"להעברת ווידג\'ט למקום אחר לוחצים עליו לחיצה ארוכה."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"כדי להעביר ווידג\'ט למקום אחר או להשתמש בפעולות מותאמות אישית, יש ללחוץ פעמיים ולא להרפות."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index a6b1a21..898fc5e 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -30,6 +30,7 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"分割画面"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s のアプリ情報"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"アプリのペア設定を保存"</string>
+    <string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"長押ししてウィジェットを移動させます。"</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"ウィジェットをダブルタップして長押ししながら移動するか、カスタム操作を使用してください。"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$dx%2$d"</string>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index 7e79faa..2b249a2 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"ეკრანის გაყოფა"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s-ის აპის ინფო"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"აპთა წყვილის შენახვა"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"შეხებით აირჩიეთ და გეჭიროთ ვიჯეტის გადასაადგილებლად."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"ორმაგი შეხებით აირჩიეთ და გეჭიროთ ვიჯეტის გადასაადგილებლად ან მორგებული მოქმედებების გამოსაყენებლად."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index cb514d8..56e562f 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Экранды бөлу"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s қолданбасы туралы ақпарат"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Қолданбаларды жұптау әрекетін сақтау"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Виджетті жылжыту үшін басып тұрыңыз."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Виджетті жылжыту үшін екі рет түртіңіз де, ұстап тұрыңыз немесе арнаулы әрекеттерді пайдаланыңыз."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index bdb9f0d..38a1fc7 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"មុខងារ​បំបែកអេក្រង់"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"ព័ត៌មានកម្មវិធី​សម្រាប់ %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"រក្សាទុកគូកម្មវិធី"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"ចុចឱ្យជាប់​ដើម្បីផ្លាស់ទី​ធាតុក្រាហ្វិក​។"</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"ចុចពីរដង រួចសង្កត់ឱ្យជាប់ ដើម្បីផ្លាស់ទី​ធាតុក្រាហ្វិក ឬប្រើ​សកម្មភាព​តាមបំណង​។"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index a35a70b..d501911 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s ಗಾಗಿ ಆ್ಯಪ್ ಮಾಹಿತಿ"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"ಆ್ಯಪ್ ಜೋಡಿ ಉಳಿಸಿ"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"ವಿಜೆಟ್ ಸರಿಸಲು ಸ್ಪರ್ಶಿಸಿ ಮತ್ತು ಹಿಡಿದುಕೊಳ್ಳಿ."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"ವಿಜೆಟ್ ಸರಿಸಲು ಅಥವಾ ಕಸ್ಟಮ್ ಕ್ರಿಯೆಗಳನ್ನು ಬಳಸಲು ಡಬಲ್-ಟ್ಯಾಪ್ ಮಾಡಿ ಮತ್ತು ಹಿಡಿದುಕೊಳ್ಳಿ."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 955af0b..1124108 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"화면 분할"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s 앱 정보"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"앱 페어링 저장"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"길게 터치하여 위젯을 이동하세요."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"두 번 탭한 다음 길게 터치하여 위젯을 이동하거나 맞춤 작업을 사용하세요."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d×%2$d"</string>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index 7d77440..17a5807 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Экранды бөлүү"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s колдонмосу жөнүндө маалымат"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Эки колдонмону бир маалда пайдаланууну сактоо"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Виджетти кое бербей басып туруп жылдырыңыз."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Виджетти жылдыруу үчүн эки жолу таптап, кармап туруңуз же ыңгайлаштырылган аракеттерди колдонуңуз."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index 9c26cfa..1cf70bf 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"ແບ່ງໜ້າຈໍ"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"ຂໍ້ມູນແອັບສຳລັບ %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"ບັນທຶກຈັບຄູ່ແອັບ"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"ແຕະຄ້າງໄວ້ເພື່ອຍ້າຍວິດເຈັດ."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"ແຕະສອງເທື່ອຄ້າງໄວ້ເພື່ອຍ້າຍວິດເຈັດ ຫຼື ໃຊ້ຄຳສັ່ງກຳນົດເອງ."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 7d69c57..84dfd39 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Išskaidyto ekrano režimas"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Programos „%1$s“ informacija"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Išsaugoti programų porą"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Dukart pal. ir palaik., kad perkeltumėte valdiklį."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Dukart palieskite ir palaikykite, kad perkeltumėte valdiklį ar naudotumėte tinkintus veiksmus."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 6dc9564..978f9e5 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Sadalīt ekrānu"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s: informācija par lietotni"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Saglabāt lietotņu pāri"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Lai pārvietotu logrīku, pieskarieties un turiet."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Lai pārvietotu logrīku, uz tā veiciet dubultskārienu un turiet. Varat arī veikt pielāgotas darbības."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index eff0e15..f70cbb6 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Поделен екран"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Податоци за апликација за %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Зачувај го парот апликации"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Допрете и задржете за да преместите виџет."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Допрете двапати и задржете за да преместите виџет или користете приспособени дејства."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index d7feb3c..e70cf43 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"സ്‌ക്രീൻ വിഭജന മോഡ്"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s എന്നതിന്റെ ആപ്പ് വിവരങ്ങൾ"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"ആപ്പ് ജോടി സംരക്ഷിക്കുക"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"വിജറ്റ് നീക്കാൻ സ്‌പർശിച്ച് പിടിക്കുക."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"വിജറ്റ് നീക്കാൻ ഡബിൾ ടാപ്പ് ചെയ്യൂ, ഹോൾഡ് ചെയ്യൂ അല്ലെങ്കിൽ ഇഷ്‌ടാനുസൃത പ്രവർത്തനങ്ങൾ ഉപയോഗിക്കൂ."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index 716bd85..5223c20 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Дэлгэцийг хуваах"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s-н аппын мэдээлэл"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Апп хослуулалтыг хадгалах"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Виджетийг зөөх бол хүрээд, удаан дарна уу."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Виджетийг зөөх эсвэл захиалгат үйлдлийг ашиглахын тулд хоёр товшоод, удаан дарна уу."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index c73295d..2836f33 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"स्प्लिट स्क्रीन"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s साठी ॲपशी संबंधित माहिती"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"ॲपची जोडी सेव्ह करा"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"विजेट हलवण्यासाठी स्पर्श करा आणि धरून ठेवा."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"विजेट हलवण्यासाठी किंवा कस्टम कृती वापरण्यासाठी दोनदा टॅप करा आणि धरून ठेवा."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index e4a2e57..5719982 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -30,6 +30,7 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Skrin pisah"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Maklumat apl untuk %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Simpan gandingan apl"</string>
+    <string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Sentuh &amp; tahan untuk menggerakkan widget."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Ketik dua kali &amp; tahan untuk menggerakkan widget atau menggunakan tindakan tersuai."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index 1ec09f5..b02dd48 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"မျက်နှာပြင် ခွဲ၍ပြသခြင်း"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s အတွက် အက်ပ်အချက်အလက်"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"အက်ပ်တွဲချိတ်ခြင်း သိမ်းရန်"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"ဝိဂျက်ကို ရွှေ့ရန် တို့ပြီး ဖိထားပါ။"</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"ဝိဂျက်ကို ရွှေ့ရန် (သို့) စိတ်ကြိုက်လုပ်ဆောင်ချက်များကို သုံးရန် နှစ်ချက်တို့ပြီး ဖိထားပါ။"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 7266a81..ba9b74f 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Delt skjerm"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Appinformasjon for %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Lagre apptilkoblingen"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Trykk og hold for å flytte en modul."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Dobbelttrykk og hold inne for å flytte en modul eller bruke tilpassede handlinger."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index a73f2a3..386d206 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"स्प्लिट स्क्रिन"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s का हकमा एपसम्बन्धी जानकारी"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"एपको पेयर सेभ गर्नुहोस्"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"कुनै विजेट सार्न डबल ट्याप गरेर छोइराख्नुहोस्।"</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"कुनै विजेट सार्न वा आफ्नो रोजाइका कारबाही प्रयोग गर्न डबल ट्याप गरेर छोइराख्नुहोस्।"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 8d2aa21..b790391 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Gesplitst scherm"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"App-info voor %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"App-paar opslaan"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Tik en houd vast om een widget te verplaatsen."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Dubbeltik en houd vast om een widget te verplaatsen of aangepaste acties te gebruiken."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 555913e..c624cf2 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"ସ୍କ୍ରିନ‌କୁ ସ୍ପ୍ଲିଟ କରନ୍ତୁ"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s ପାଇଁ ଆପ ସୂଚନା"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"ଆପ ପେୟାର ସେଭ କରନ୍ତୁ"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"ଏକ ୱିଜେଟକୁ ମୁଭ୍ କରିବା ପାଇଁ ସ୍ପର୍ଶ କରି ଧରି ରଖନ୍ତୁ।"</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"ଏକ ୱିଜେଟକୁ ମୁଭ୍ କରିବା ପାଇଁ ଦୁଇଥର-ଟାପ୍ କରି ଧରି ରଖନ୍ତୁ କିମ୍ବା କଷ୍ଟମ୍ କାର୍ଯ୍ୟଗୁଡ଼ିକୁ ବ୍ୟବହାର କରନ୍ତୁ।"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index e6e3f07..b09569e 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s ਲਈ ਐਪ ਜਾਣਕਾਰੀ"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"ਐਪ ਜੋੜਾਬੱਧ ਰੱਖਿਅਤ ਕਰੋ"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"ਕਿਸੇ ਵਿਜੇਟ ਨੂੰ ਲਿਜਾਉਣ ਲਈ ਸਪਰਸ਼ ਕਰਕੇ ਰੱਖੋ।"</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"ਵਿਜੇਟ ਲਿਜਾਉਣ ਲਈ ਜਾਂ ਵਿਉਂਂਤੀਆਂ ਕਾਰਵਾਈਆਂ ਵਰਤਣ ਲਈ ਦੋ ਵਾਰ ਟੈਪ ਕਰਕੇ ਦਬਾ ਕੇ ਰੱਖੋ।"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 739ef6c..e2c77ef 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Podziel ekran"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Informacje o aplikacji: %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Zapisz parę aplikacji"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Naciśnij i przytrzymaj, aby przenieść widżet."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Naciśnij dwukrotnie i przytrzymaj, aby przenieść widżet lub użyć działań niestandardowych."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 76c65a4..6da2e46 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Ecrã dividido"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Informações da app para %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Guardar par de apps"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Toque sem soltar para mover um widget."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Toque duas vezes sem soltar para mover um widget ou utilizar ações personalizadas."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index e23f459..e61f0d4 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -30,6 +30,7 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Tela dividida"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Informações do app %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Salvar par de apps"</string>
+    <string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Toque e pressione para mover um widget."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Toque duas vezes e mantenha a tela pressionada para mover um widget ou usar ações personalizadas."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index fc9c5d1..c014d19 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Ecran împărțit"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Informații despre aplicație pentru %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Salvează perechea de aplicații"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Atinge și ține apăsat pentru a muta un widget."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Atinge de două ori și ține apăsat pentru a muta un widget sau folosește acțiuni personalizate."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index b3ff865..00079dd 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Разделить экран"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Сведения о приложении \"%1$s\""</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Сохранить настройки одновременного использования двух приложений"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Чтобы переместить виджет, нажмите на него и удерживайте"</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Чтобы использовать специальные действия или перенести виджет, нажмите на него дважды и удерживайте."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d x %2$d"</string>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index 6f93989..a9b9d6a 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"බෙදුම් තිරය"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s සඳහා යෙදුම් තතු"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"යෙදුම් යුගල සුරකින්න"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"විජට් එකක් ගෙන යාමට ස්පර්ශ කර අල්ලා ගෙන සිටින්න."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"විජට් එකක් ගෙන යාමට හෝ අභිරුචි ක්‍රියා භාවිත කිරීමට දෙවරක් තට්ටු කර අල්ලා ගෙන සිටින්න."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 742d726..db85ed1 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Rozdeliť obrazovku"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Informácie o aplikácii pre %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Uložiť pár aplikácií"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Pridržaním presuňte miniaplikáciu."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Dvojitým klepnutím a pridržaním presuňte miniaplikáciu alebo použite vlastné akcie."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 57289d3..18c4946 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -30,6 +30,7 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Razdeljen zaslon"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Podatki o aplikaciji za: %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Shrani par aplikacij"</string>
+    <string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Pridržite pripomoček, da ga premaknete."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Dvakrat se dotaknite pripomočka in ga pridržite, da ga premaknete, ali pa uporabite dejanja po meri."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index 7dee8aa..c4828ba 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Ekrani i ndarë"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Informacioni i aplikacionit për %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Ruaj çiftin e aplikacioneve"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Prek dhe mbaj shtypur një miniaplikacion për ta zhvendosur."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Trokit dy herë dhe mbaje shtypur një miniapliikacion për ta zhvendosur atë ose për të përdorur veprimet e personalizuara."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 7121c53..e472bde 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Подељени екран"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Информације о апликацији за: %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Сачувај пар апликација"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Додирните и задржите ради померања виџета."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Двапут додирните и задржите да бисте померали виџет или користите прилагођене радње."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d×%2$d"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index aeaf708..45b3d0f 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Delad skärm"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Appinformation för %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Spara appar som ska användas tillsammans"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Tryck länge för att flytta en widget."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Tryck snabbt två gånger och håll kvar för att flytta en widget eller använda anpassade åtgärder."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 5be3bf3..e7a5b1a 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Gawa skrini"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Maelezo ya programu ya %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Hifadhi jozi ya programu"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Gusa na ushikilie ili usogeze wijeti."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Gusa mara mbili na ushikilie ili usogeze wijeti au utumie vitendo maalum."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index ec287f5..9b8d36b 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"திரைப் பிரிப்பு"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$sக்கான ஆப்ஸ் தகவல்கள்"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"ஆப்ஸ் ஜோடியைச் சேமி"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"விட்ஜெட்டை நகர்த்தத் தொட்டுப் பிடிக்கவும்."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"விட்ஜெட்டை நகர்த்த இருமுறை தட்டிப் பிடிக்கவும் அல்லது பிரத்தியேகச் செயல்களைப் பயன்படுத்தவும்."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 032a241..2dbe222 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"స్ప్లిట్ స్క్రీన్"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s కోసం యాప్ సమాచారం"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"యాప్ పెయిర్‌ను సేవ్ చేయండి"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"విడ్జెట్‌ను తరలించడానికి తాకి &amp; నొక్కి ఉంచండి."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"విడ్జెట్‌ను తరలించడానికి లేదా అనుకూల చర్యలను ఉపయోగించడానికి రెండుసార్లు నొక్కండి &amp; హోల్డ్ చేయి."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index fe79b12..ab3aca8 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"แยกหน้าจอ"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"ข้อมูลแอปสำหรับ %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"บันทึกคู่แอป"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"แตะค้างไว้เพื่อย้ายวิดเจ็ต"</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"แตะสองครั้งค้างไว้เพื่อย้ายวิดเจ็ตหรือใช้การดำเนินการที่กำหนดเอง"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index fe4aac1..2786d7a 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Split screen"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Impormasyon ng app para sa %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"I-save ang pares ng app"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Pindutin nang matagal para ilipat ang widget."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"I-double tap at pindutin nang matagal para ilipat ang widget o gumamit ng mga custom na pagkilos."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 8a836e6..4de1ece 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Bölünmüş ekran"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s uygulama bilgileri"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Uygulama çiftini kaydedin"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Widget\'ı taşımak için dokunup basılı tutun."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Widget\'ı taşımak veya özel işlemleri kullanmak için iki kez dokunup basılı tutun."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 5dc5d8e..a11a253 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Розділити екран"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Інформація про додаток для %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Зберегти пару додатків"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Натисніть і втримуйте, щоб перемістити віджет."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Двічі натисніть і втримуйте віджет, щоб перемістити його або виконати інші дії."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index 3994810..1d0c7cb 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"اسپلٹ اسکرین"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"‏%1$s کے لیے ایپ کی معلومات"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"ایپس کے جوڑے کو محفوظ کریں"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"ویجیٹ منتقل کرنے کے لیے ٹچ کریں اور پکڑ کر رکھیں۔"</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"ویجیٹ کو منتقل کرنے یا حسب ضرورت کارروائیاں استعمال کرنے کے لیے دوبار تھپتھپائیں اور پکڑ کر رکھیں۔"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index 405c4ef..b9c0e8a 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Ekranni ikkiga ajratish"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s ilovasi axboroti"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Ilova juftini saqlash"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Vidjetni bosib turgan holatda suring."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Ikki marta bosib va bosib turgan holatda vidjetni tanlang yoki maxsus amaldan foydalaning."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 075a7b0..a520d9a 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Chia đôi màn hình"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Thông tin ứng dụng cho %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Lưu cặp ứng dụng"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Chạm và giữ để di chuyển một tiện ích."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Nhấn đúp và giữ để di chuyển một tiện ích hoặc sử dụng các thao tác tùy chỉnh."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index e525c02..5671984 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"分屏"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s 的应用信息"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"保存应用对"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"轻触并按住即可移动微件。"</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"点按两次并按住微件即可移动该微件或使用自定义操作。"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index 0f0960e..d7dd77c 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"分割螢幕"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s 的應用程式資料"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"儲存應用程式配對"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"輕觸並按住即可移動小工具。"</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"㩒兩下之後㩒住,就可以郁小工具或者用自訂操作。"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index cf40155..314b94b 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"分割畫面"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"「%1$s」的應用程式資訊"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"儲存應用程式配對"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"按住即可移動小工具。"</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"輕觸兩下並按住即可移動小工具或使用自訂操作。"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 6ccc4be..50e25ac 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Hlukanisa isikrini"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Ulwazi lwe-App ye-%1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Londoloza i-app ebhangqiwe"</string>
+    <!-- no translation found for app_pair_default_title (4045241727446873529) -->
+    <skip />
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Thinta uphinde ubambe ukuze uhambise iwijethi."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Thepha kabili uphinde ubambe ukuze uhambise iwijethi noma usebenzise izindlela ezingokwezifiso."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 7661bd7..070d024 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -245,8 +245,7 @@
     <dimen name="keyboard_drag_stroke_width">4dp</dimen>
 
     <!-- Folders -->
-    <dimen name="page_indicator_dot_size">8dp</dimen>
-    <dimen name="page_indicator_dot_size_v2">6dp</dimen>
+    <dimen name="page_indicator_dot_size">6dp</dimen>
     <dimen name="page_indicator_size">10dp</dimen>
 
 
diff --git a/res/values/strings.xml b/res/values/strings.xml
index a2f4a61..57163ff 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -44,6 +44,8 @@
 
     <!-- App pairs -->
     <string name="save_app_pair">Save app pair</string>
+    <!-- App pair default title -->
+    <string name="app_pair_default_title"><xliff:g id="app1" example="Chrome">%1$s</xliff:g> | <xliff:g id="app2" example="YouTube">%2$s</xliff:g></string>
 
     <!-- Widgets -->
     <!-- Message to tell the user to press and hold on a widget to add it [CHAR_LIMIT=50] -->
@@ -248,6 +250,10 @@
     <!-- Folder name format when folder has 4 or more items shown in preview-->
     <string name="folder_name_format_overflow">Folder: <xliff:g id="name" example="Games">%1$s</xliff:g>, <xliff:g id="size" example="2">%2$d</xliff:g> or more items</string>
 
+    <!-- App pair accessibility -->
+    <!-- App pair name -->
+    <string name="app_pair_name_format">App pair: <xliff:g id="app1" example="Chrome">%1$s</xliff:g> and <xliff:g id="app2" example="YouTube">%2$s</xliff:g></string>
+
     <!-- Strings for the customization mode -->
     <!-- Text for wallpaper change button [CHAR LIMIT=30]-->
     <string name="styles_wallpaper_button_text">Wallpaper &amp; style</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 4cb6414..82a227a 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -409,6 +409,12 @@
         <item name="android:windowTranslucentStatus">true</item>
     </style>
 
+    <style name="ProxyActivityStarterTheme" parent="@android:style/Theme.Translucent.NoTitleBar">
+        <item name="android:navigationBarColor">@android:color/transparent</item>
+        <item name="android:statusBarColor">@android:color/transparent</item>
+        <item name="android:windowDrawsSystemBarBackgrounds">true</item>
+    </style>
+
     <style name="FolderStyleDefault">
         <item name="folderTopPadding">24dp</item>
         <item name="folderCellHeight">94dp</item>
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index 7b0d71b..189db21 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -3,6 +3,7 @@
 import static com.android.launcher3.CellLayout.SPRING_LOADED_PROGRESS;
 import static com.android.launcher3.LauncherAnimUtils.LAYOUT_HEIGHT;
 import static com.android.launcher3.LauncherAnimUtils.LAYOUT_WIDTH;
+import static com.android.launcher3.LauncherPrefs.RECONFIGURABLE_WIDGET_EDUCATION_TIP_SEEN;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGET_RESIZE_COMPLETED;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGET_RESIZE_STARTED;
 import static com.android.launcher3.views.BaseDragLayer.LAYOUT_X;
@@ -55,8 +56,6 @@
     private static final float RESIZE_THRESHOLD = 0.66f;
     private static final int RESIZE_TRANSITION_DURATION_MS = 150;
 
-    private static final String KEY_RECONFIGURABLE_WIDGET_EDUCATION_TIP_SEEN =
-            "launcher.reconfigurable_widget_education_tip_seen";
     private static final Rect sTmpRect = new Rect();
     private static final Rect sTmpRect2 = new Rect();
 
@@ -276,9 +275,8 @@
             if (!hasSeenReconfigurableWidgetEducationTip()) {
                 post(() -> {
                     if (showReconfigurableWidgetEducationTip() != null) {
-                        mLauncher.getSharedPrefs().edit()
-                                .putBoolean(KEY_RECONFIGURABLE_WIDGET_EDUCATION_TIP_SEEN,
-                                        true).apply();
+                        LauncherPrefs.get(getContext()).put(
+                                RECONFIGURABLE_WIDGET_EDUCATION_TIP_SEEN, true);
                     }
                 });
             }
@@ -872,8 +870,7 @@
     }
 
     private boolean hasSeenReconfigurableWidgetEducationTip() {
-        return mLauncher.getSharedPrefs()
-                .getBoolean(KEY_RECONFIGURABLE_WIDGET_EDUCATION_TIP_SEEN, false)
+        return LauncherPrefs.get(getContext()).get(RECONFIGURABLE_WIDGET_EDUCATION_TIP_SEEN)
                 || Utilities.isRunningInTestHarness();
     }
 }
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index ab9836f..d7b50a0 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -17,7 +17,6 @@
 package com.android.launcher3;
 
 import static android.text.Layout.Alignment.ALIGN_NORMAL;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_DOWNLOAD_APP_UX_V2;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_ICON_LABEL_AUTO_SCALING;
 import static com.android.launcher3.config.FeatureFlags.enableCursorHoverStates;
 import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon;
@@ -387,10 +386,12 @@
         setTag(itemInfo);
     }
 
+    @VisibleForTesting
     @UiThread
-    protected void applyIconAndLabel(ItemInfoWithIcon info) {
+    public void applyIconAndLabel(ItemInfoWithIcon info) {
         int flags = shouldUseTheme() ? FLAG_THEMED : 0;
-        if (mHideBadge) {
+        // Remove badge on icons smaller than 48dp.
+        if (mHideBadge || mDisplay == DISPLAY_SEARCH_RESULT_SMALL) {
             flags |= FLAG_NO_BADGE;
         }
         FastBitmapDrawable iconDrawable = info.newIcon(getContext(), flags);
@@ -814,6 +815,8 @@
         float currentWordWidth, runningWidth = 0;
         CharSequence currentWord;
         StringBuilder newString = new StringBuilder();
+        // TODO: Remove when ENABLE_ICON_LABEL_AUTO_SCALING feature flag is being cleaned up.
+        paint.setLetterSpacing(MIN_LETTER_SPACING);
         int stringPtr = 0;
         for (int i = 0; i < breakPoints.size()+1; i++) {
             if (i < breakPoints.size()) {
@@ -878,7 +881,7 @@
             if ((info.runtimeStatusFlags & FLAG_INCREMENTAL_DOWNLOAD_ACTIVE) != 0
                     || info.hasPromiseIconUi()
                     || (info.runtimeStatusFlags & FLAG_INSTALL_SESSION_ACTIVE) != 0
-                    || (ENABLE_DOWNLOAD_APP_UX_V2.get() && icon != null)) {
+                    || (icon != null)) {
                 updateProgressBarUi(info.getProgressLevel() == 100 ? icon : null);
             }
         }
@@ -915,9 +918,7 @@
             if (mIcon instanceof PreloadIconDrawable) {
                 preloadIconDrawable = (PreloadIconDrawable) mIcon;
                 preloadIconDrawable.setLevel(progressLevel);
-                preloadIconDrawable.setIsDisabled(ENABLE_DOWNLOAD_APP_UX_V2.get()
-                        ? info.getProgressLevel() == 0
-                        : !info.isAppStartable());
+                preloadIconDrawable.setIsDisabled(info.getProgressLevel() == 0);
             } else {
                 preloadIconDrawable = makePreloadIcon();
                 setIcon(preloadIconDrawable);
@@ -942,9 +943,7 @@
         final PreloadIconDrawable preloadDrawable = newPendingIcon(getContext(), info);
 
         preloadDrawable.setLevel(progressLevel);
-        preloadDrawable.setIsDisabled(ENABLE_DOWNLOAD_APP_UX_V2.get()
-                ? info.getProgressLevel() == 0
-                : !info.isAppStartable());
+        preloadDrawable.setIsDisabled(info.getProgressLevel() == 0);
         return preloadDrawable;
     }
 
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 08e5def..91fbe53 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -117,6 +117,8 @@
     // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
     @Thunk final int[] mTmpPoint = new int[2];
     @Thunk final int[] mTempLocation = new int[2];
+
+    @Thunk final Rect mTempOnDrawCellToRect = new Rect();
     final PointF mTmpPointF = new PointF();
 
     protected GridOccupancy mOccupied;
@@ -600,23 +602,18 @@
         DeviceProfile dp = mActivity.getDeviceProfile();
         int paddingX = Math.min((mCellWidth - dp.iconSizePx) / 2, dp.gridVisualizationPaddingX);
         int paddingY = Math.min((mCellHeight - dp.iconSizePx) / 2, dp.gridVisualizationPaddingY);
-        mVisualizeGridRect.set(paddingX, paddingY,
-                mCellWidth - paddingX,
-                mCellHeight - paddingY);
 
         mVisualizeGridPaint.setStrokeWidth(8);
-        int paintAlpha = (int) (120 * mGridAlpha);
-        mVisualizeGridPaint.setColor(ColorUtils.setAlphaComponent(mGridColor, paintAlpha));
 
+        // This is used for debugging purposes only
         if (mVisualizeCells) {
+            int paintAlpha = (int) (120 * mGridAlpha);
+            mVisualizeGridPaint.setColor(ColorUtils.setAlphaComponent(mGridColor, paintAlpha));
             for (int i = 0; i < mCountX; i++) {
                 for (int j = 0; j < mCountY; j++) {
-                    int transX = i * mCellWidth + (i * mBorderSpace.x) + getPaddingLeft()
-                            + paddingX;
-                    int transY = j * mCellHeight + (j * mBorderSpace.y) + getPaddingTop()
-                            + paddingY;
-
-                    mVisualizeGridRect.offsetTo(transX, transY);
+                    cellToRect(i, j, 1, 1, mTempOnDrawCellToRect);
+                    mVisualizeGridRect.set(mTempOnDrawCellToRect);
+                    mVisualizeGridRect.inset(paddingX, paddingY);
                     mVisualizeGridPaint.setStyle(Paint.Style.FILL);
                     canvas.drawRoundRect(mVisualizeGridRect, mGridVisualizationRoundingRadius,
                             mGridVisualizationRoundingRadius, mVisualizeGridPaint);
@@ -628,25 +625,13 @@
             for (int i = 0; i < mDragOutlines.length; i++) {
                 final float alpha = mDragOutlineAlphas[i];
                 if (alpha <= 0) continue;
+                CellLayoutLayoutParams params = mDragOutlines[i];
+                cellToRect(params.getCellX(), params.getCellY(), params.cellHSpan, params.cellVSpan,
+                        mTempOnDrawCellToRect);
+                mVisualizeGridRect.set(mTempOnDrawCellToRect);
+                mVisualizeGridRect.inset(paddingX, paddingY);
 
                 mVisualizeGridPaint.setAlpha(255);
-                int x = mDragOutlines[i].getCellX();
-                int y = mDragOutlines[i].getCellY();
-                int spanX = mDragOutlines[i].cellHSpan;
-                int spanY = mDragOutlines[i].cellVSpan;
-
-                // TODO b/194414754 clean this up, reconcile with cellToRect
-                mVisualizeGridRect.set(paddingX, paddingY,
-                        mCellWidth * spanX + mBorderSpace.x * (spanX - 1) - paddingX,
-                        mCellHeight * spanY + mBorderSpace.y * (spanY - 1) - paddingY);
-
-                int transX = x * mCellWidth + (x * mBorderSpace.x)
-                        + getPaddingLeft() + paddingX;
-                int transY = y * mCellHeight + (y * mBorderSpace.y)
-                        + getPaddingTop() + paddingY;
-
-                mVisualizeGridRect.offsetTo(transX, transY);
-
                 mVisualizeGridPaint.setStyle(Paint.Style.STROKE);
                 mVisualizeGridPaint.setColor(Color.argb((int) (alpha),
                         Color.red(mGridColor), Color.green(mGridColor), Color.blue(mGridColor)));
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 94eb7a3..c96e22d 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -705,6 +705,17 @@
     }
 
     /**
+     * Return maximum of all apps row count displayed on screen. Note that 1) Partially displayed
+     * row is counted as 1 row, and 2) we don't exclude the space of floating search bar. This
+     * method is used for calculating number of {@link BubbleTextView} we need to pre-inflate. Thus
+     * reasonable over estimation is fine.
+     */
+    public int getMaxAllAppsRowCount() {
+        return (int) (Math.ceil((availableHeightPx - allAppsTopPadding)
+                / (float) allAppsCellHeightPx));
+    }
+
+    /**
      * QSB width is always calculated because when in 3 button nav the width doesn't follow the
      * width of the hotseat.
      */
@@ -1761,7 +1772,7 @@
 
     /** Gets the space that the overview actions will take, including bottom margin. */
     public int getOverviewActionsClaimedSpace() {
-        int overviewActionsSpace = isTablet && FeatureFlags.enableGridOnlyOverview()
+        int overviewActionsSpace = isTablet && Flags.enableGridOnlyOverview()
                 ? 0
                 : (overviewActionsTopMarginPx + overviewActionsHeight);
         return overviewActionsSpace + getOverviewActionsClaimedSpaceBelow();
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 2b433f1..4a9add4 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -26,8 +26,9 @@
 import static com.android.launcher3.AbstractFloatingView.TYPE_FOLDER;
 import static com.android.launcher3.AbstractFloatingView.TYPE_ICON_SURFACE;
 import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
-import static com.android.launcher3.AbstractFloatingView.TYPE_SNACKBAR;
 import static com.android.launcher3.AbstractFloatingView.getTopOpenViewWithType;
+import static com.android.launcher3.BuildConfig.APPLICATION_ID;
+import static com.android.launcher3.BuildConfig.QSB_ON_FIRST_SCREEN;
 import static com.android.launcher3.LauncherAnimUtils.HOTSEAT_SCALE_PROPERTY_FACTORY;
 import static com.android.launcher3.LauncherAnimUtils.SCALE_INDEX_WIDGET_TRANSITION;
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
@@ -43,9 +44,11 @@
 import static com.android.launcher3.LauncherState.NO_SCALE;
 import static com.android.launcher3.LauncherState.SPRING_LOADED;
 import static com.android.launcher3.Utilities.postAsyncCallback;
+import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_SMARTSPACE_REMOVAL;
 import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE;
 import static com.android.launcher3.config.FeatureFlags.MULTI_SELECT_EDIT_MODE;
-import static com.android.launcher3.config.FeatureFlags.SHOW_DOT_PAGINATION;
+import static com.android.launcher3.config.FeatureFlags.shouldShowFirstPageWidget;
 import static com.android.launcher3.logging.StatsLogManager.EventEnum;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
@@ -182,7 +185,6 @@
 import com.android.launcher3.pageindicators.WorkspacePageIndicator;
 import com.android.launcher3.pm.PinRequestHelper;
 import com.android.launcher3.popup.ArrowPopup;
-import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.popup.PopupDataProvider;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.qsb.QsbContainerView;
@@ -204,10 +206,8 @@
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.KeyboardShortcutsDelegate;
 import com.android.launcher3.util.LockedUserState;
-import com.android.launcher3.util.OnboardingPrefs;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.PendingRequestArgs;
-import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.RunnableList;
 import com.android.launcher3.util.ScreenOnTracker;
 import com.android.launcher3.util.ScreenOnTracker.ScreenOnListener;
@@ -236,7 +236,6 @@
 import com.android.launcher3.widget.picker.WidgetsFullSheet;
 import com.android.systemui.plugins.LauncherOverlayPlugin;
 import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.plugins.shared.LauncherExterns;
 import com.android.systemui.plugins.shared.LauncherOverlayManager;
 import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlay;
 
@@ -258,7 +257,7 @@
  * Default launcher application.
  */
 public class Launcher extends StatefulActivity<LauncherState>
-        implements LauncherExterns, Callbacks, InvariantDeviceProfile.OnIDPChangeListener,
+        implements Callbacks, InvariantDeviceProfile.OnIDPChangeListener,
         PluginListener<LauncherOverlayPlugin> {
     public static final String TAG = "Launcher";
 
@@ -321,9 +320,11 @@
     @Thunk @VisibleForTesting public static final int NEW_APPS_ANIMATION_DELAY = 500;
 
     private static final String DISPLAY_WORKSPACE_TRACE_METHOD_NAME = "DisplayWorkspaceFirstFrame";
-    private static final String DISPLAY_ALL_APPS_TRACE_METHOD_NAME = "DisplayAllApps";
+    public static final String DISPLAY_ALL_APPS_TRACE_METHOD_NAME = "DisplayAllApps";
     public static final int DISPLAY_WORKSPACE_TRACE_COOKIE = 0;
     public static final int DISPLAY_ALL_APPS_TRACE_COOKIE = 1;
+    private static final String COLD_STARTUP_TRACE_METHOD_NAME = "LauncherColdStartup";
+    public static final int COLD_STARTUP_TRACE_COOKIE = 2;
 
     private static final FloatProperty<Workspace<?>> WORKSPACE_WIDGET_SCALE =
             WORKSPACE_SCALE_PROPERTY_FACTORY.get(SCALE_INDEX_WIDGET_TRANSITION);
@@ -333,7 +334,11 @@
     private static final boolean DESKTOP_MODE_SUPPORTED =
             "1".equals(Utilities.getSystemProperty("persist.wm.debug.desktop_mode_2", "0"));
 
-    KeyboardShortcutsDelegate mKeyboardShortcutsDelegate = new KeyboardShortcutsDelegate(this);
+    private final ModelCallbacks mModelCallbacks = createModelCallbacks();
+
+    private final KeyboardShortcutsDelegate mKeyboardShortcutsDelegate =
+            new KeyboardShortcutsDelegate(this);
+
     @Thunk
     Workspace<?> mWorkspace;
     @Thunk
@@ -378,13 +383,9 @@
 
     private PopupDataProvider mPopupDataProvider;
 
-    private IntSet mSynchronouslyBoundPages = new IntSet();
-    @NonNull private IntSet mPagesToBindSynchronously = new IntSet();
-
     // We only want to get the SharedPreferences once since it does an FS stat each time we get
     // it from the context.
     private SharedPreferences mSharedPrefs;
-    private OnboardingPrefs<? extends Launcher> mOnboardingPrefs;
 
     // Activity result which needs to be processed after workspace has loaded.
     private ActivityResultInfo mPendingActivityResult;
@@ -420,11 +421,14 @@
     private BaseSearchConfig mBaseSearchConfig;
     private StartupLatencyLogger mStartupLatencyLogger;
     private CellPosMapper mCellPosMapper = CellPosMapper.DEFAULT;
+    private boolean mIsFirstPagePinnedItemEnabled = QSB_ON_FIRST_SCREEN
+            && !ENABLE_SMARTSPACE_REMOVAL.get();
 
     private final CannedAnimationCoordinator mAnimationCoordinator =
             new CannedAnimationCoordinator(this);
 
     private final List<BackPressHandler> mBackPressedHandlers = new ArrayList<>();
+    private boolean mIsColdStartupAfterReboot;
 
     public static Launcher getLauncher(Context context) {
         return fromContext(context);
@@ -439,6 +443,14 @@
                             ? COLD
                             : COLD_DEVICE_REBOOTING
                         : WARM);
+
+        mIsColdStartupAfterReboot = sIsNewProcess
+            && !LockedUserState.get(this).isUserUnlockedAtLauncherStartup();
+        if (mIsColdStartupAfterReboot) {
+            Trace.beginAsyncSection(
+                    COLD_STARTUP_TRACE_METHOD_NAME, COLD_STARTUP_TRACE_COOKIE);
+        }
+
         sIsNewProcess = false;
         mStartupLatencyLogger
                 .logStart(LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION)
@@ -520,8 +532,6 @@
         mAllAppsController = new AllAppsTransitionController(this);
         mStateManager = new StateManager<>(this, NORMAL);
 
-        mOnboardingPrefs = createOnboardingPrefs(mSharedPrefs);
-
         // TODO: move the SearchConfig to SearchState when new LauncherState is created.
         mBaseSearchConfig = new BaseSearchConfig();
 
@@ -547,7 +557,7 @@
         if (savedInstanceState != null) {
             int[] pageIds = savedInstanceState.getIntArray(RUNTIME_STATE_CURRENT_SCREEN_IDS);
             if (pageIds != null) {
-                mPagesToBindSynchronously = IntSet.wrap(pageIds);
+                mModelCallbacks.setPagesToBindSynchronously(IntSet.wrap(pageIds));
             }
         }
 
@@ -576,9 +586,6 @@
         getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW,
                 Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText));
 
-        if (mLauncherCallbacks != null) {
-            mLauncherCallbacks.onCreate(savedInstanceState);
-        }
         mOverlayManager = getDefaultOverlay();
         PluginManagerWrapper.INSTANCE.get(this).addPluginListener(this,
                 LauncherOverlayPlugin.class, false /* allowedMultiple */);
@@ -593,6 +600,10 @@
         mStartupLatencyLogger.logEnd(LAUNCHER_LATENCY_STARTUP_ACTIVITY_ON_CREATE);
     }
 
+    protected ModelCallbacks createModelCallbacks() {
+        return new ModelCallbacks(this);
+    }
+
     /**
      * Create {@link StartupLatencyLogger} that only collects launcher startup latency metrics
      * without sending them anywhere. Child class can override this method to create logger
@@ -670,14 +681,9 @@
         return new LauncherOverlayManager() { };
     }
 
-    protected OnboardingPrefs<? extends Launcher> createOnboardingPrefs(
-            SharedPreferences sharedPrefs) {
-        return new OnboardingPrefs<>(this, sharedPrefs);
-    }
-
     @Override
     public void onPluginConnected(LauncherOverlayPlugin overlayManager, Context context) {
-        switchOverlay(() -> overlayManager.createOverlayManager(this, this));
+        switchOverlay(() -> overlayManager.createOverlayManager(this));
     }
 
     @Override
@@ -788,8 +794,6 @@
         return true;
     }
 
-    private LauncherCallbacks mLauncherCallbacks;
-
     @Override
     public void invalidateParent(ItemInfo info) {
         if (info.container >= 0) {
@@ -1304,7 +1308,9 @@
         // Until the workspace is bound, ensure that we keep the wallpaper offset locked to the
         // default state, otherwise we will update to the wrong offsets in RTL
         mWorkspace.lockWallpaperToDefaultPage();
-        mWorkspace.bindAndInitFirstWorkspaceScreen();
+        if (!ENABLE_SMARTSPACE_REMOVAL.get()) {
+            mWorkspace.bindAndInitFirstWorkspaceScreen();
+        }
         mDragController.addDragListener(mWorkspace);
 
         // Get the search/delete/uninstall bar
@@ -1321,18 +1327,14 @@
         mDropTargetBar.setup(mDragController);
         mAllAppsController.setupViews(mScrimView, mAppsView);
 
-        if (SHOW_DOT_PAGINATION.get()) {
-            mWorkspace.getPageIndicator().setShouldAutoHide(true);
-            mWorkspace.getPageIndicator().setPaintColor(
-                    Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText)
-                            ? Color.BLACK
-                            : Color.WHITE);
-        }
+        mWorkspace.getPageIndicator().setShouldAutoHide(true);
+        mWorkspace.getPageIndicator().setPaintColor(Themes.getAttrBoolean(
+                this, R.attr.isWorkspaceDarkText) ? Color.BLACK : Color.WHITE);
     }
 
     @Override
     public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
-        if (SHOW_DOT_PAGINATION.get() && WorkspacePageIndicator.class.getName().equals(name)) {
+        if (WorkspacePageIndicator.class.getName().equals(name)) {
             return LayoutInflater.from(context).inflate(R.layout.page_indicator_dots,
                     (ViewGroup) parent, false);
         }
@@ -1581,9 +1583,6 @@
                 }
             }
 
-            if (mLauncherCallbacks != null) {
-                mLauncherCallbacks.onHomeIntent(internalStateHandled);
-            }
             if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
                 handleSplitAnimationGoingToHome();
             }
@@ -1646,8 +1645,9 @@
     @Override
     public void onRestoreInstanceState(Bundle state) {
         super.onRestoreInstanceState(state);
-        if (mSynchronouslyBoundPages != null) {
-            mSynchronouslyBoundPages.forEach(screenId -> {
+        IntSet synchronouslyBoundPages = mModelCallbacks.getSynchronouslyBoundPages();
+        if (synchronouslyBoundPages != null) {
+            synchronouslyBoundPages.forEach(screenId -> {
                 int pageIndex = mWorkspace.getPageIndexForScreenId(screenId);
                 if (pageIndex != PagedView.INVALID_PAGE) {
                     mWorkspace.restoreInstanceStateForChild(pageIndex);
@@ -1752,28 +1752,6 @@
         }
     }
 
-    /**
-     * Indicates that we want global search for this activity by setting the globalSearch
-     * argument for {@link #startSearch} to true.
-     */
-    @Override
-    public void startSearch(String initialQuery, boolean selectInitialQuery,
-            Bundle appSearchData, boolean globalSearch) {
-        if (appSearchData == null) {
-            appSearchData = new Bundle();
-            appSearchData.putString("source", "launcher-search");
-        }
-
-        if (mLauncherCallbacks == null ||
-                !mLauncherCallbacks.startSearch(initialQuery, selectInitialQuery, appSearchData)) {
-            // Starting search from the callbacks failed. Start the default global search.
-            super.startSearch(initialQuery, selectInitialQuery, appSearchData, true);
-        }
-
-        // We need to show the workspace after starting the search
-        mStateManager.goToState(NORMAL);
-    }
-
     void addAppWidgetFromDropImpl(int appWidgetId, ItemInfo info, AppWidgetHostView boundWidget,
             WidgetAddFlowHandler addFlowHandler) {
         if (LOGD) {
@@ -2092,41 +2070,7 @@
 
     @Override
     public IntSet getPagesToBindSynchronously(IntArray orderedScreenIds) {
-        IntSet visibleIds;
-        if (!mPagesToBindSynchronously.isEmpty()) {
-            visibleIds = mPagesToBindSynchronously;
-        } else if (!mWorkspaceLoading) {
-            visibleIds = mWorkspace.getCurrentPageScreenIds();
-        } else {
-            // If workspace binding is still in progress, getCurrentPageScreenIds won't be accurate,
-            // and we should use mSynchronouslyBoundPages that's set during initial binding.
-            visibleIds = mSynchronouslyBoundPages;
-        }
-        IntArray actualIds = new IntArray();
-
-        IntSet result = new IntSet();
-        if (visibleIds.isEmpty()) {
-            return result;
-        }
-        for (int id : orderedScreenIds.toArray()) {
-            actualIds.add(id);
-        }
-        int firstId = visibleIds.getArray().get(0);
-        int pairId = mWorkspace.getScreenPair(firstId);
-        // Double check that actual screenIds contains the visibleId, as empty screens are hidden
-        // in single panel.
-        if (actualIds.contains(firstId)) {
-            result.add(firstId);
-            if (mDeviceProfile.isTwoPanels && actualIds.contains(pairId)) {
-                result.add(pairId);
-            }
-        } else if (LauncherAppState.getIDP(this).supportedProfiles.stream().anyMatch(
-                deviceProfile -> deviceProfile.isTwoPanels) && actualIds.contains(pairId)) {
-            // Add the right panel if left panel is hidden when switching display, due to empty
-            // pages being hidden in single panel.
-            result.add(pairId);
-        }
-        return result;
+        return mModelCallbacks.getPagesToBindSynchronously(orderedScreenIds);
     }
 
     /**
@@ -2174,14 +2118,24 @@
     }
 
     @Override
+    public void setIsFirstPagePinnedItemEnabled(boolean isFirstPagePinnedItemEnabled) {
+        mIsFirstPagePinnedItemEnabled = isFirstPagePinnedItemEnabled;
+        mWorkspace.bindAndInitFirstWorkspaceScreen();
+    }
+
+    @Override
     public void bindScreens(IntArray orderedScreenIds) {
         mWorkspace.mPageIndicator.setAreScreensBinding(true);
         int firstScreenPosition = 0;
-        if (FeatureFlags.QSB_ON_FIRST_SCREEN &&
-                orderedScreenIds.indexOf(Workspace.FIRST_SCREEN_ID) != firstScreenPosition) {
-            orderedScreenIds.removeValue(Workspace.FIRST_SCREEN_ID);
-            orderedScreenIds.add(firstScreenPosition, Workspace.FIRST_SCREEN_ID);
-        } else if (!FeatureFlags.QSB_ON_FIRST_SCREEN && orderedScreenIds.isEmpty()) {
+        if ((FeatureFlags.QSB_ON_FIRST_SCREEN
+                && mIsFirstPagePinnedItemEnabled
+                && !shouldShowFirstPageWidget())
+                && orderedScreenIds.indexOf(FIRST_SCREEN_ID) != firstScreenPosition) {
+            orderedScreenIds.removeValue(FIRST_SCREEN_ID);
+            orderedScreenIds.add(firstScreenPosition, FIRST_SCREEN_ID);
+        } else if (((!FeatureFlags.QSB_ON_FIRST_SCREEN && !mIsFirstPagePinnedItemEnabled)
+                || shouldShowFirstPageWidget())
+                && orderedScreenIds.isEmpty()) {
             // If there are no screens, we need to have an empty screen
             mWorkspace.addExtraEmptyScreens();
         }
@@ -2229,7 +2183,10 @@
         int count = orderedScreenIds.size();
         for (int i = 0; i < count; i++) {
             int screenId = orderedScreenIds.get(i);
-            if (FeatureFlags.QSB_ON_FIRST_SCREEN && screenId == Workspace.FIRST_SCREEN_ID) {
+            if (FeatureFlags.QSB_ON_FIRST_SCREEN
+                    && mIsFirstPagePinnedItemEnabled
+                    && !shouldShowFirstPageWidget()
+                    && screenId == FIRST_SCREEN_ID) {
                 // No need to bind the first screen, as its always bound.
                 continue;
             }
@@ -2239,13 +2196,7 @@
 
     @Override
     public void preAddApps() {
-        // If there's an undo snackbar, force it to complete to ensure empty screens are removed
-        // before trying to add new items.
-        mModelWriter.commitDelete();
-        AbstractFloatingView snackbar = AbstractFloatingView.getOpenView(this, TYPE_SNACKBAR);
-        if (snackbar != null) {
-            snackbar.post(() -> snackbar.close(true));
-        }
+        mModelCallbacks.preAddApps();
     }
 
     @Override
@@ -2621,8 +2572,8 @@
     @TargetApi(Build.VERSION_CODES.S)
     public void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks,
             int workspaceItemCount, boolean isBindSync) {
-        mSynchronouslyBoundPages = boundPages;
-        mPagesToBindSynchronously = new IntSet();
+        mModelCallbacks.setSynchronouslyBoundPages(boundPages);
+        mModelCallbacks.setPagesToBindSynchronously(new IntSet());
 
         clearPendingBinds();
         ViewOnDrawExecutor executor = new ViewOnDrawExecutor(pendingTasks);
@@ -2659,6 +2610,11 @@
                                 .logEnd(LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION)
                                 .log()
                                 .reset();
+                        if (mIsColdStartupAfterReboot) {
+                            Trace.endAsyncSection(COLD_STARTUP_TRACE_METHOD_NAME,
+                                    COLD_STARTUP_TRACE_COOKIE);
+                        }
+
                         MAIN_EXECUTOR.getHandler().postAtFrontOfQueue(
                                 () -> getRootView().getViewTreeObserver()
                                         .removeOnDrawListener(this));
@@ -2690,7 +2646,7 @@
         // Since we are just resetting the current page without user interaction,
         // override the previous page so we don't log the page switch.
         mWorkspace.setCurrentPage(currentPage, currentPage /* overridePrevPage */);
-        mPagesToBindSynchronously = new IntSet();
+        mModelCallbacks.setPagesToBindSynchronously(new IntSet());
 
         // Cache one page worth of icons
         getViewCache().setCacheSize(R.layout.folder_application,
@@ -2843,90 +2799,100 @@
     public void onPageEndTransition() {}
 
     /**
-     * Add the icons for all apps.
-     *
-     * Implementation of the method from LauncherModel.Callbacks.
+     * See {@code LauncherBindingDelegate}
      */
     @Override
     @TargetApi(Build.VERSION_CODES.S)
     @UiThread
     public void bindAllApplications(AppInfo[] apps, int flags,
             Map<PackageUserKey, Integer> packageUserKeytoUidMap) {
-        Preconditions.assertUIThread();
-        boolean hadWorkApps = mAppsView.shouldShowTabs();
-        AllAppsStore<Launcher> appsStore = mAppsView.getAppsStore();
-        appsStore.setApps(apps, flags, packageUserKeytoUidMap);
-        PopupContainerWithArrow.dismissInvalidPopup(this);
-        if (hadWorkApps != mAppsView.shouldShowTabs()) {
-            getStateManager().goToState(NORMAL);
-        }
-
+        mModelCallbacks.bindAllApplications(apps, flags, packageUserKeytoUidMap);
         if (Utilities.ATLEAST_S) {
-            Trace.endAsyncSection(DISPLAY_ALL_APPS_TRACE_METHOD_NAME,
-                    DISPLAY_ALL_APPS_TRACE_COOKIE);
+            Trace.endAsyncSection(
+                    Launcher.DISPLAY_ALL_APPS_TRACE_METHOD_NAME,
+                    Launcher.DISPLAY_ALL_APPS_TRACE_COOKIE
+            );
         }
     }
 
     /**
-     * Copies LauncherModel's map of activities to shortcut counts to Launcher's. This is necessary
-     * because LauncherModel's map is updated in the background, while Launcher runs on the UI.
+     * See {@code LauncherBindingDelegate}
      */
     @Override
     public void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMapCopy) {
-        mPopupDataProvider.setDeepShortcutMap(deepShortcutMapCopy);
+        mModelCallbacks.bindDeepShortcutMap(deepShortcutMapCopy);
     }
 
     @Override
     public void bindIncrementalDownloadProgressUpdated(AppInfo app) {
-        mAppsView.getAppsStore().updateProgressBar(app);
+        mModelCallbacks.bindIncrementalDownloadProgressUpdated(app);
     }
 
     @Override
     public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets) {
-        mWorkspace.widgetsRestored(widgets);
+        mModelCallbacks.bindWidgetsRestored(widgets);
     }
 
     /**
-     * Some shortcuts were updated in the background.
-     * Implementation of the method from LauncherModel.Callbacks.
-     *
-     * @param updated list of shortcuts which have changed.
+     * See {@code LauncherBindingDelegate}
      */
     @Override
     public void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) {
-        if (!updated.isEmpty()) {
-            mWorkspace.updateWorkspaceItems(updated, this);
-            PopupContainerWithArrow.dismissInvalidPopup(this);
-        }
+        mModelCallbacks.bindWorkspaceItemsChanged(updated);
     }
 
     /**
-     * Update the state of a package, typically related to install state.
-     *
-     * Implementation of the method from LauncherModel.Callbacks.
+     * See {@code LauncherBindingDelegate}
      */
     @Override
     public void bindRestoreItemsChange(HashSet<ItemInfo> updates) {
-        mWorkspace.updateRestoreItems(updates, this);
+        mModelCallbacks.bindRestoreItemsChange(updates);
     }
 
     /**
-     * A package was uninstalled/updated.  We take both the super set of packageNames
-     * in addition to specific applications to remove, the reason being that
-     * this can be called when a package is updated as well.  In that scenario,
-     * we only remove specific components from the workspace and hotseat, where as
-     * package-removal should clear all items by package name.
+     * See {@code LauncherBindingDelegate}
      */
     @Override
     public void bindWorkspaceComponentsRemoved(Predicate<ItemInfo> matcher) {
-        mWorkspace.removeItemsByMatcher(matcher);
-        mDragController.onAppsRemoved(matcher);
-        PopupContainerWithArrow.dismissInvalidPopup(this);
+        mModelCallbacks.bindWorkspaceComponentsRemoved(matcher);
+    }
+
+    /**
+     * See {@code LauncherBindingDelegate}
+     */
+    @Override
+    public void bindAllWidgets(final List<WidgetsListBaseEntry> allWidgets) {
+        mModelCallbacks.bindAllWidgets(allWidgets);
     }
 
     @Override
-    public void bindAllWidgets(final List<WidgetsListBaseEntry> allWidgets) {
-        mPopupDataProvider.setAllWidgets(allWidgets);
+    public void bindSmartspaceWidget() {
+        CellLayout cl = mWorkspace.getScreenWithId(FIRST_SCREEN_ID);
+        int spanX = InvariantDeviceProfile.INSTANCE.get(this).numSearchContainerColumns;
+        if (cl != null) {
+            for (int col = 0; col < spanX; col++) {
+                if (cl.isOccupied(col, 0)) {
+                    return;
+                }
+            }
+        } else {
+            return;
+        }
+
+        WidgetsListBaseEntry widgetsListBaseEntry = getPopupDataProvider()
+                .getAllWidgets().stream().filter(
+                        item -> item.mPkgItem.packageName.equals(
+                                APPLICATION_ID))
+                .findFirst()
+                .orElse(null);
+        if (widgetsListBaseEntry != null) {
+            LauncherAppWidgetProviderInfo launcherAppWidgetProviderInfo =
+                    widgetsListBaseEntry.mWidgets.get(0).widgetInfo;
+            PendingAddWidgetInfo info = new PendingAddWidgetInfo(launcherAppWidgetProviderInfo,
+                    CONTAINER_DESKTOP);
+            addPendingItem(info, info.container, FIRST_SCREEN_ID, new int[]{0, 0}, info.spanX,
+                    info.spanY);
+        }
     }
 
     @Override
@@ -2959,7 +2925,7 @@
                 for (int j = 0; j < layout.getChildCount(); j++) {
                     Object tag = layout.getChildAt(j).getTag();
                     if (tag != null) {
-                        writer.println(prefix + "    " + tag.toString());
+                        writer.println(prefix + "    " + tag);
                     }
                 }
             }
@@ -2969,7 +2935,7 @@
             for (int j = 0; j < layout.getChildCount(); j++) {
                 Object tag = layout.getChildAt(j).getTag();
                 if (tag != null) {
-                    writer.println(prefix + "    " + tag.toString());
+                    writer.println(prefix + "    " + tag);
                 }
             }
         }
@@ -2997,10 +2963,6 @@
         }
 
         mModel.dumpState(prefix, fd, writer, args);
-
-        if (mLauncherCallbacks != null) {
-            mLauncherCallbacks.dump(prefix, fd, writer, args);
-        }
         mOverlayManager.dump(prefix, writer);
     }
 
@@ -3103,11 +3065,16 @@
     }
 
     /** To be overridden by subclasses */
-    protected boolean isSplitSelectionEnabled() {
+    public boolean isSplitSelectionEnabled() {
         // Overridden
         return false;
     }
 
+    /** Call to dismiss the intermediary split selection state. */
+    public void dismissSplitSelection() {
+        // Overridden; move this into ActivityContext if necessary for Taskbar
+    }
+
     @Override
     public void returnToHomescreen() {
         super.returnToHomescreen();
@@ -3239,16 +3206,10 @@
     /**
      * Call this after onCreate to set or clear overlay.
      */
-    @Override
     public void setLauncherOverlay(LauncherOverlay overlay) {
         mWorkspace.setLauncherOverlay(overlay);
     }
 
-    public boolean setLauncherCallbacks(LauncherCallbacks callbacks) {
-        mLauncherCallbacks = callbacks;
-        return true;
-    }
-
     /**
      * Persistent callback which notifies when an activity launch is deferred because the activity
      * was not yet resumed.
@@ -3262,11 +3223,7 @@
      * @param pages should not be null.
      */
     public void setPagesToBindSynchronously(@NonNull IntSet pages) {
-        mPagesToBindSynchronously = pages;
-    }
-
-    public OnboardingPrefs<? extends Launcher> getOnboardingPrefs() {
-        return mOnboardingPrefs;
+        mModelCallbacks.setPagesToBindSynchronously(pages);
     }
 
     @Override
@@ -3352,16 +3309,10 @@
         return mModelWriter;
     }
 
-    @Override
     public SharedPreferences getSharedPrefs() {
         return mSharedPrefs;
     }
 
-    @Override
-    public SharedPreferences getDevicePrefs() {
-        return LauncherPrefs.getDevicePrefs(this);
-    }
-
     public int getOrientation() {
         return mOldConfig.orientation;
     }
@@ -3416,6 +3367,10 @@
         // Overridden
     }
 
+    public boolean getIsFirstPagePinnedItemEnabled() {
+        return mIsFirstPagePinnedItemEnabled;
+    }
+
     /**
      * Returns the animation coordinator for playing one-off animations
      */
@@ -3443,4 +3398,4 @@
     }
 
     // End of Getters and Setters
-}
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 9db8c82..8d19040 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -21,6 +21,8 @@
 
 import static com.android.launcher3.LauncherPrefs.ICON_STATE;
 import static com.android.launcher3.LauncherPrefs.THEMED_ICONS;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_SMARTSPACE_REMOVAL;
+import static com.android.launcher3.model.LoaderTask.SMARTSPACE_ON_HOME_SCREEN;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 import static com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI;
 
@@ -64,6 +66,10 @@
     public static final String ACTION_FORCE_ROLOAD = "force-reload-launcher";
     public static final String KEY_ICON_STATE = "pref_icon_shape_path";
     public static final String KEY_ALL_APPS_OVERVIEW_THRESHOLD = "pref_all_apps_overview_threshold";
+    public static final String KEY_LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE =
+            "pref_long_press_nav_handle_slop_multiplier";
+    public static final String KEY_LONG_PRESS_NAV_HANDLE_TIMEOUT_MS =
+            "pref_long_press_nav_handle_timeout_ms";
 
     // We do not need any synchronization for this variable as its only written on UI thread.
     public static final MainThreadInitializedObject<LauncherAppState> INSTANCE =
@@ -123,6 +129,23 @@
                 .addUserEventListener(mModel::onUserEvent);
         mOnTerminateCallback.add(userChangeListener::close);
 
+        if (ENABLE_SMARTSPACE_REMOVAL.get()) {
+            OnSharedPreferenceChangeListener firstPagePinnedItemListener =
+                    new OnSharedPreferenceChangeListener() {
+                        @Override
+                        public void onSharedPreferenceChanged(
+                                SharedPreferences sharedPreferences, String key) {
+                            if (SMARTSPACE_ON_HOME_SCREEN.equals(key)) {
+                                mModel.forceReload();
+                            }
+                        }
+                    };
+            LauncherPrefs.getPrefs(mContext).registerOnSharedPreferenceChangeListener(
+                    firstPagePinnedItemListener);
+            mOnTerminateCallback.add(() -> LauncherPrefs.getPrefs(mContext)
+                    .unregisterOnSharedPreferenceChangeListener(firstPagePinnedItemListener));
+        }
+
         LockedUserState.get(context).runOnUserUnlocked(() -> {
             CustomWidgetManager cwm = CustomWidgetManager.INSTANCE.get(mContext);
             cwm.setWidgetRefreshCallback(mModel::refreshAndBindWidgetsAndShortcuts);
diff --git a/src/com/android/launcher3/LauncherCallbacks.java b/src/com/android/launcher3/LauncherCallbacks.java
deleted file mode 100644
index 0e529bd..0000000
--- a/src/com/android/launcher3/LauncherCallbacks.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import android.os.Bundle;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
-/**
- * LauncherCallbacks is an interface used to extend the Launcher activity. It includes many hooks
- * in order to add additional functionality. Some of these are very general, and give extending
- * classes the ability to react to Activity life-cycle or specific user interactions. Others
- * are more specific and relate to replacing parts of the application, for example, the search
- * interface or the wallpaper picker.
- */
-public interface LauncherCallbacks {
-
-    /*
-     * Activity life-cycle methods. These methods are triggered after
-     * the code in the corresponding Launcher method is executed.
-     */
-    void onCreate(Bundle savedInstanceState);
-    void dump(String prefix, FileDescriptor fd, PrintWriter w, String[] args);
-    void onHomeIntent(boolean internalStateHandled);
-
-    /**
-     * Starts a search with {@param initialQuery}. Return false if search was not started.
-     */
-    boolean startSearch(
-            String initialQuery, boolean selectInitialQuery, Bundle appSearchData);
-}
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index e8d5116..9a0d02a 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -20,10 +20,11 @@
 import android.content.SharedPreferences
 import android.content.SharedPreferences.OnSharedPreferenceChangeListener
 import android.util.Log
+import android.view.ViewConfiguration
 import androidx.annotation.VisibleForTesting
+import com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN
 import com.android.launcher3.LauncherFiles.DEVICE_PREFERENCES_KEY
 import com.android.launcher3.LauncherFiles.SHARED_PREFERENCES_KEY
-import com.android.launcher3.allapps.WorkProfileManager
 import com.android.launcher3.model.DeviceGridState
 import com.android.launcher3.pm.InstallSessionHelper
 import com.android.launcher3.provider.RestoreDbTask
@@ -59,11 +60,11 @@
                 IS_STARTUP_DATA_MIGRATED.defaultValue
             )
 
-    // TODO: Remove `item == TASKBAR_PINNING` once isBootAwareStartupDataEnabled is always true
     private fun chooseSharedPreferences(item: Item): SharedPreferences =
         if (
-            (isBootAwareStartupDataEnabled && item.isBootAware && isStartupDataMigrated) ||
-                item == TASKBAR_PINNING
+            (moveStartupDataToDeviceProtectedStorageIsEnabled &&
+                item.encryptionType == EncryptionType.MOVE_TO_DEVICE_PROTECTED &&
+                isStartupDataMigrated) || item.encryptionType == EncryptionType.DEVICE_PROTECTED
         )
             bootAwarePrefs
         else item.encryptedPrefs
@@ -138,13 +139,20 @@
     private fun prepareToPutValues(
         updates: Array<out Pair<Item, Any>>
     ): List<SharedPreferences.Editor> {
-        val updatesPerPrefFile = updates.groupBy { it.first.encryptedPrefs }.toMutableMap()
+        val updatesPerPrefFile =
+            updates
+                .filter { it.first.encryptionType != EncryptionType.DEVICE_PROTECTED }
+                .groupBy { it.first.encryptedPrefs }
+                .toMutableMap()
 
-        if (isBootAwareStartupDataEnabled) {
-            val bootAwareUpdates = updates.filter { it.first.isBootAware }
-            if (bootAwareUpdates.isNotEmpty()) {
-                updatesPerPrefFile[bootAwarePrefs] = bootAwareUpdates
+        val bootAwareUpdates =
+            updates.filter {
+                (it.first.encryptionType == EncryptionType.MOVE_TO_DEVICE_PROTECTED &&
+                    moveStartupDataToDeviceProtectedStorageIsEnabled) ||
+                    it.first.encryptionType == EncryptionType.DEVICE_PROTECTED
             }
+        if (bootAwareUpdates.isNotEmpty()) {
+            updatesPerPrefFile[bootAwarePrefs] = bootAwareUpdates
         }
 
         return updatesPerPrefFile.map { prefToItemValueList ->
@@ -237,13 +245,20 @@
      *   .apply() or .commit()
      */
     private fun prepareToRemove(items: Array<out Item>): List<SharedPreferences.Editor> {
-        val itemsPerFile = items.groupBy { it.encryptedPrefs }.toMutableMap()
+        val itemsPerFile =
+            items
+                .filter { it.encryptionType != EncryptionType.DEVICE_PROTECTED }
+                .groupBy { it.encryptedPrefs }
+                .toMutableMap()
 
-        if (isBootAwareStartupDataEnabled) {
-            val bootAwareUpdates = items.filter { it.isBootAware }
-            if (bootAwareUpdates.isNotEmpty()) {
-                itemsPerFile[bootAwarePrefs] = bootAwareUpdates
+        val bootAwareUpdates =
+            items.filter {
+                (it.encryptionType == EncryptionType.MOVE_TO_DEVICE_PROTECTED &&
+                    moveStartupDataToDeviceProtectedStorageIsEnabled) ||
+                    it.encryptionType == EncryptionType.DEVICE_PROTECTED
             }
+        if (bootAwareUpdates.isNotEmpty()) {
+            itemsPerFile[bootAwarePrefs] = bootAwareUpdates
         }
 
         return itemsPerFile.map { (prefs, items) ->
@@ -254,7 +269,7 @@
     }
 
     fun migrateStartupDataToDeviceProtectedStorage() {
-        if (!isBootAwareStartupDataEnabled) return
+        if (!moveStartupDataToDeviceProtectedStorageIsEnabled) return
 
         Log.d(
             TAG,
@@ -263,7 +278,7 @@
         )
 
         with(bootAwarePrefs.edit()) {
-            BOOT_AWARE_ITEMS.forEach { putValue(it, get(it)) }
+            ITEMS_TO_MOVE_TO_DEVICE_PROTECTED_STORAGE.forEach { putValue(it, get(it)) }
             putBoolean(IS_STARTUP_DATA_MIGRATED.sharedPrefKey, true)
             apply()
         }
@@ -278,27 +293,81 @@
         @JvmStatic fun get(context: Context): LauncherPrefs = INSTANCE.get(context)
 
         const val TASKBAR_PINNING_KEY = "TASKBAR_PINNING_KEY"
-        @JvmField val ICON_STATE = nonRestorableItem(LauncherAppState.KEY_ICON_STATE, "", true)
+        const val SHOULD_SHOW_SMARTSPACE_KEY = "SHOULD_SHOW_SMARTSPACE_KEY"
+        @JvmField
+        val ICON_STATE =
+            nonRestorableItem(
+                LauncherAppState.KEY_ICON_STATE,
+                "",
+                EncryptionType.MOVE_TO_DEVICE_PROTECTED
+            )
         @JvmField
         val ALL_APPS_OVERVIEW_THRESHOLD =
-            nonRestorableItem(LauncherAppState.KEY_ALL_APPS_OVERVIEW_THRESHOLD, 180, true)
-        @JvmField val THEMED_ICONS = backedUpItem(Themes.KEY_THEMED_ICONS, false, true)
+            nonRestorableItem(
+                LauncherAppState.KEY_ALL_APPS_OVERVIEW_THRESHOLD,
+                180,
+                EncryptionType.MOVE_TO_DEVICE_PROTECTED
+            )
+        @JvmField
+        val LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE =
+            nonRestorableItem(
+                LauncherAppState.KEY_LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE,
+                100,
+                EncryptionType.MOVE_TO_DEVICE_PROTECTED
+            )
+        @JvmField
+        val LONG_PRESS_NAV_HANDLE_TIMEOUT_MS =
+            nonRestorableItem(
+                LauncherAppState.KEY_LONG_PRESS_NAV_HANDLE_TIMEOUT_MS,
+                ViewConfiguration.getLongPressTimeout(),
+                EncryptionType.MOVE_TO_DEVICE_PROTECTED
+            )
+        @JvmField
+        val THEMED_ICONS =
+            backedUpItem(Themes.KEY_THEMED_ICONS, false, EncryptionType.MOVE_TO_DEVICE_PROTECTED)
         @JvmField val PROMISE_ICON_IDS = backedUpItem(InstallSessionHelper.PROMISE_ICON_IDS, "")
-        @JvmField val WORK_EDU_STEP = backedUpItem(WorkProfileManager.KEY_WORK_EDU_STEP, 0)
-        @JvmField val WORKSPACE_SIZE = backedUpItem(DeviceGridState.KEY_WORKSPACE_SIZE, "", true)
-        @JvmField val HOTSEAT_COUNT = backedUpItem(DeviceGridState.KEY_HOTSEAT_COUNT, -1, true)
-        @JvmField val TASKBAR_PINNING = backedUpItem(TASKBAR_PINNING_KEY, false, true)
+        @JvmField val WORK_EDU_STEP = backedUpItem("showed_work_profile_edu", 0)
+        @JvmField
+        val WORKSPACE_SIZE =
+            backedUpItem(
+                DeviceGridState.KEY_WORKSPACE_SIZE,
+                "",
+                EncryptionType.MOVE_TO_DEVICE_PROTECTED
+            )
+        @JvmField
+        val HOTSEAT_COUNT =
+            backedUpItem(
+                DeviceGridState.KEY_HOTSEAT_COUNT,
+                -1,
+                EncryptionType.MOVE_TO_DEVICE_PROTECTED
+            )
+        @JvmField
+        val TASKBAR_PINNING =
+            backedUpItem(TASKBAR_PINNING_KEY, false, EncryptionType.DEVICE_PROTECTED)
 
         @JvmField
         val DEVICE_TYPE =
-            backedUpItem(DeviceGridState.KEY_DEVICE_TYPE, InvariantDeviceProfile.TYPE_PHONE, true)
-        @JvmField val DB_FILE = backedUpItem(DeviceGridState.KEY_DB_FILE, "", true)
+            backedUpItem(
+                DeviceGridState.KEY_DEVICE_TYPE,
+                InvariantDeviceProfile.TYPE_PHONE,
+                EncryptionType.MOVE_TO_DEVICE_PROTECTED
+            )
+        @JvmField
+        val DB_FILE =
+            backedUpItem(DeviceGridState.KEY_DB_FILE, "", EncryptionType.MOVE_TO_DEVICE_PROTECTED)
+        @JvmField
+        val SHOULD_SHOW_SMARTSPACE =
+            backedUpItem(
+                SHOULD_SHOW_SMARTSPACE_KEY,
+                WIDGET_ON_FIRST_SCREEN,
+                EncryptionType.DEVICE_PROTECTED
+            )
         @JvmField
         val RESTORE_DEVICE =
             backedUpItem(
                 RestoreDbTask.RESTORED_DEVICE_TYPE,
                 InvariantDeviceProfile.TYPE_PHONE,
-                true
+                EncryptionType.MOVE_TO_DEVICE_PROTECTED
             )
         @JvmField val APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_IDS, "")
         @JvmField val OLD_APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_OLD_IDS, "")
@@ -308,7 +377,7 @@
                 "idp_grid_name",
                 isBackedUp = true,
                 defaultValue = null,
-                isBootAware = true,
+                encryptionType = EncryptionType.MOVE_TO_DEVICE_PROTECTED,
                 type = String::class.java
             )
         @JvmField
@@ -322,41 +391,49 @@
                 "is_startup_data_boot_aware",
                 isBackedUp = false,
                 defaultValue = false,
-                isBootAware = true
+                encryptionType = EncryptionType.DEVICE_PROTECTED
             )
 
-        @VisibleForTesting
+        // Preferences for widget configurations
+        @JvmField
+        val RECONFIGURABLE_WIDGET_EDUCATION_TIP_SEEN =
+            backedUpItem("launcher.reconfigurable_widget_education_tip_seen", false)
+        @JvmField
+        val WIDGETS_EDUCATION_DIALOG_SEEN =
+            backedUpItem("launcher.widgets_education_dialog_seen", false)
+        @JvmField
+        val WIDGETS_EDUCATION_TIP_SEEN = backedUpItem("launcher.widgets_education_tip_seen", false)
+
         @JvmStatic
         fun <T> backedUpItem(
             sharedPrefKey: String,
             defaultValue: T,
-            isBootAware: Boolean = false
+            encryptionType: EncryptionType = EncryptionType.ENCRYPTED
         ): ConstantItem<T> =
-            ConstantItem(sharedPrefKey, isBackedUp = true, defaultValue, isBootAware)
+            ConstantItem(sharedPrefKey, isBackedUp = true, defaultValue, encryptionType)
 
         @JvmStatic
         fun <T> backedUpItem(
             sharedPrefKey: String,
             type: Class<out T>,
-            isBootAware: Boolean = false,
+            encryptionType: EncryptionType = EncryptionType.ENCRYPTED,
             defaultValueFromContext: (c: Context) -> T
         ): ContextualItem<T> =
             ContextualItem(
                 sharedPrefKey,
                 isBackedUp = true,
                 defaultValueFromContext,
-                isBootAware,
+                encryptionType,
                 type
             )
 
-        @VisibleForTesting
         @JvmStatic
         fun <T> nonRestorableItem(
             sharedPrefKey: String,
             defaultValue: T,
-            isBootAware: Boolean = false
+            encryptionType: EncryptionType = EncryptionType.ENCRYPTED
         ): ConstantItem<T> =
-            ConstantItem(sharedPrefKey, isBackedUp = false, defaultValue, isBootAware)
+            ConstantItem(sharedPrefKey, isBackedUp = false, defaultValue, encryptionType)
 
         @Deprecated("Don't use shared preferences directly. Use other LauncherPref methods.")
         @JvmStatic
@@ -381,17 +458,16 @@
 }
 
 // It is a var because the unit tests are setting this to true so they can run.
-@VisibleForTesting
-var isBootAwareStartupDataEnabled: Boolean =
-    com.android.launcher3.config.FeatureFlags.ENABLE_BOOT_AWARE_STARTUP_DATA.get()
+var moveStartupDataToDeviceProtectedStorageIsEnabled: Boolean =
+    com.android.launcher3.config.FeatureFlags.MOVE_STARTUP_DATA_TO_DEVICE_PROTECTED_STORAGE.get()
 
-private val BOOT_AWARE_ITEMS: MutableSet<ConstantItem<*>> = mutableSetOf()
+private val ITEMS_TO_MOVE_TO_DEVICE_PROTECTED_STORAGE: MutableSet<ConstantItem<*>> = mutableSetOf()
 
 abstract class Item {
     abstract val sharedPrefKey: String
     abstract val isBackedUp: Boolean
     abstract val type: Class<*>
-    abstract val isBootAware: Boolean
+    abstract val encryptionType: EncryptionType
     val sharedPrefFile: String
         get() = if (isBackedUp) SHARED_PREFERENCES_KEY else DEVICE_PREFERENCES_KEY
 
@@ -402,22 +478,27 @@
     override val sharedPrefKey: String,
     override val isBackedUp: Boolean,
     val defaultValue: T,
-    override val isBootAware: Boolean,
+    override val encryptionType: EncryptionType,
     // The default value can be null. If so, the type needs to be explicitly stated, or else NPE
     override val type: Class<out T> = defaultValue!!::class.java
 ) : Item() {
     init {
-        if (isBootAware && isBootAwareStartupDataEnabled) {
-            BOOT_AWARE_ITEMS.add(this)
+        if (
+            encryptionType == EncryptionType.MOVE_TO_DEVICE_PROTECTED &&
+                moveStartupDataToDeviceProtectedStorageIsEnabled
+        ) {
+            ITEMS_TO_MOVE_TO_DEVICE_PROTECTED_STORAGE.add(this)
         }
     }
+
+    fun get(c: Context): T = LauncherPrefs.get(c).get(this)
 }
 
 data class ContextualItem<T>(
     override val sharedPrefKey: String,
     override val isBackedUp: Boolean,
     private val defaultSupplier: (c: Context) -> T,
-    override val isBootAware: Boolean,
+    override val encryptionType: EncryptionType,
     override val type: Class<out T>
 ) : Item() {
     private var default: T? = null
@@ -428,4 +509,12 @@
         }
         return default!!
     }
+
+    fun get(c: Context): T = LauncherPrefs.get(c).get(this)
+}
+
+enum class EncryptionType {
+    ENCRYPTED,
+    DEVICE_PROTECTED,
+    MOVE_TO_DEVICE_PROTECTED
 }
diff --git a/src/com/android/launcher3/ModelCallbacks.kt b/src/com/android/launcher3/ModelCallbacks.kt
new file mode 100644
index 0000000..8304e96
--- /dev/null
+++ b/src/com/android/launcher3/ModelCallbacks.kt
@@ -0,0 +1,138 @@
+package com.android.launcher3
+
+import androidx.annotation.UiThread
+import com.android.launcher3.model.BgDataModel
+import com.android.launcher3.model.data.AppInfo
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.LauncherAppWidgetInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.popup.PopupContainerWithArrow
+import com.android.launcher3.util.ComponentKey
+import com.android.launcher3.util.IntArray as LIntArray
+import com.android.launcher3.util.IntSet as LIntSet
+import com.android.launcher3.util.PackageUserKey
+import com.android.launcher3.util.Preconditions
+import com.android.launcher3.widget.model.WidgetsListBaseEntry
+import java.util.function.Predicate
+
+class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks {
+
+    var synchronouslyBoundPages = LIntSet()
+    var pagesToBindSynchronously = LIntSet()
+
+    override fun preAddApps() {
+        // If there's an undo snackbar, force it to complete to ensure empty screens are removed
+        // before trying to add new items.
+        launcher.modelWriter.commitDelete()
+        val snackbar =
+            AbstractFloatingView.getOpenView<AbstractFloatingView>(
+                launcher,
+                AbstractFloatingView.TYPE_SNACKBAR
+            )
+        snackbar?.post { snackbar.close(true) }
+    }
+
+    @UiThread
+    override fun bindAllApplications(
+        apps: Array<AppInfo?>?,
+        flags: Int,
+        packageUserKeytoUidMap: Map<PackageUserKey?, Int?>?
+    ) {
+        Preconditions.assertUIThread()
+        val hadWorkApps = launcher.appsView.shouldShowTabs()
+        launcher.appsView.appsStore.setApps(apps, flags, packageUserKeytoUidMap)
+        PopupContainerWithArrow.dismissInvalidPopup(launcher)
+        if (hadWorkApps != launcher.appsView.shouldShowTabs()) {
+            launcher.stateManager.goToState(LauncherState.NORMAL)
+        }
+    }
+
+    /**
+     * Copies LauncherModel's map of activities to shortcut counts to Launcher's. This is necessary
+     * because LauncherModel's map is updated in the background, while Launcher runs on the UI.
+     */
+    override fun bindDeepShortcutMap(deepShortcutMapCopy: HashMap<ComponentKey?, Int?>?) {
+        launcher.popupDataProvider.setDeepShortcutMap(deepShortcutMapCopy)
+    }
+
+    override fun bindIncrementalDownloadProgressUpdated(app: AppInfo?) {
+        launcher.appsView.appsStore.updateProgressBar(app)
+    }
+
+    override fun bindWidgetsRestored(widgets: ArrayList<LauncherAppWidgetInfo?>?) {
+        launcher.workspace.widgetsRestored(widgets)
+    }
+
+    /**
+     * Some shortcuts were updated in the background. Implementation of the method from
+     * LauncherModel.Callbacks.
+     *
+     * @param updated list of shortcuts which have changed.
+     */
+    override fun bindWorkspaceItemsChanged(updated: List<WorkspaceItemInfo?>) {
+        if (updated.isNotEmpty()) {
+            launcher.workspace.updateWorkspaceItems(updated, launcher)
+            PopupContainerWithArrow.dismissInvalidPopup(launcher)
+        }
+    }
+
+    /**
+     * Update the state of a package, typically related to install state. Implementation of the
+     * method from LauncherModel.Callbacks.
+     */
+    override fun bindRestoreItemsChange(updates: HashSet<ItemInfo?>?) {
+        launcher.workspace.updateRestoreItems(updates, launcher)
+    }
+
+    /**
+     * A package was uninstalled/updated. We take both the super set of packageNames in addition to
+     * specific applications to remove, the reason being that this can be called when a package is
+     * updated as well. In that scenario, we only remove specific components from the workspace and
+     * hotseat, where as package-removal should clear all items by package name.
+     */
+    override fun bindWorkspaceComponentsRemoved(matcher: Predicate<ItemInfo?>?) {
+        launcher.workspace.removeItemsByMatcher(matcher)
+        launcher.dragController.onAppsRemoved(matcher)
+        PopupContainerWithArrow.dismissInvalidPopup(launcher)
+    }
+
+    override fun bindAllWidgets(allWidgets: List<WidgetsListBaseEntry?>?) {
+        launcher.popupDataProvider.allWidgets = allWidgets
+    }
+
+    /** Returns the ids of the workspaces to bind. */
+    override fun getPagesToBindSynchronously(orderedScreenIds: LIntArray): LIntSet {
+        // If workspace binding is still in progress, getCurrentPageScreenIds won't be
+        // accurate, and we should use mSynchronouslyBoundPages that's set during initial binding.
+        val visibleIds =
+            when {
+                !pagesToBindSynchronously.isEmpty -> pagesToBindSynchronously
+                !launcher.isWorkspaceLoading -> launcher.workspace.currentPageScreenIds
+                else -> synchronouslyBoundPages
+            }
+        // Launcher IntArray has the same name as Kotlin IntArray
+        val result = LIntSet()
+        if (visibleIds.isEmpty) {
+            return result
+        }
+        val actualIds = orderedScreenIds.clone()
+        val firstId = visibleIds.first()
+        val pairId = launcher.workspace.getScreenPair(firstId)
+        // Double check that actual screenIds contains the visibleId, as empty screens are hidden
+        // in single panel.
+        if (actualIds.contains(firstId)) {
+            result.add(firstId)
+            if (launcher.deviceProfile.isTwoPanels && actualIds.contains(pairId)) {
+                result.add(pairId)
+            }
+        } else if (
+            LauncherAppState.getIDP(launcher).supportedProfiles.any(DeviceProfile::isTwoPanels) &&
+                actualIds.contains(pairId)
+        ) {
+            // Add the right panel if left panel is hidden when switching display, due to empty
+            // pages being hidden in single panel.
+            result.add(pairId)
+        }
+        return result
+    }
+}
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 5ce88a3..f355ae7 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -1573,7 +1573,9 @@
     @Override
     public void requestChildFocus(View child, View focused) {
         super.requestChildFocus(child, focused);
-
+        if (!shouldHandleRequestChildFocus()) {
+            return;
+        }
         // In case the device is controlled by a controller, mCurrentPage isn't updated properly
         // which results in incorrect navigation
         int nextPage = getNextPage();
@@ -1587,6 +1589,10 @@
         }
     }
 
+    protected boolean shouldHandleRequestChildFocus() {
+        return true;
+    }
+
     public int getDestinationPage() {
         return getDestinationPage(mOrientationHandler.getPrimaryScroll(this));
     }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index a1a3974..eeb5fe0 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -28,7 +28,9 @@
 import static com.android.launcher3.LauncherState.SPRING_LOADED;
 import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe;
 import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_SMARTSPACE_REMOVAL;
 import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE;
+import static com.android.launcher3.config.FeatureFlags.shouldShowFirstPageWidget;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPELEFT;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPERIGHT;
@@ -71,6 +73,7 @@
 import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
 import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
 import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.apppairs.AppPairIcon;
 import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.celllayout.CellPosMapper;
 import com.android.launcher3.celllayout.CellPosMapper.CellPos;
@@ -594,17 +597,19 @@
      * Initializes and binds the first page
      */
     public void bindAndInitFirstWorkspaceScreen() {
-        if (!FeatureFlags.QSB_ON_FIRST_SCREEN) {
+        if ((!FeatureFlags.QSB_ON_FIRST_SCREEN
+                || !mLauncher.getIsFirstPagePinnedItemEnabled())
+                || shouldShowFirstPageWidget()) {
+            mFirstPagePinnedItem = null;
             return;
         }
 
         // Add the first page
         CellLayout firstPage = insertNewWorkspaceScreen(Workspace.FIRST_SCREEN_ID, getChildCount());
-        // Always add a first page pinned widget on the first screen.
         if (mFirstPagePinnedItem == null) {
             // In transposed layout, we add the first page pinned widget in the Grid.
             // As workspace does not touch the edges, we do not need a full
-            // width first page pinned widget.
+            // width first page pinned item.
             mFirstPagePinnedItem = LayoutInflater.from(getContext())
                     .inflate(R.layout.search_container_workspace, firstPage, false);
         }
@@ -624,7 +629,7 @@
         // transition animations competing with us changing the scroll when we add pages
         disableLayoutTransitions();
 
-        // Recycle the first page pinned widget
+        // Recycle the first page pinned item
         if (mFirstPagePinnedItem != null) {
             ((ViewGroup) mFirstPagePinnedItem.getParent()).removeView(mFirstPagePinnedItem);
         }
@@ -635,12 +640,14 @@
         mScreenOrder.clear();
         mWorkspaceScreens.clear();
 
+        // Ensure that the first page is always present
+        if (!ENABLE_SMARTSPACE_REMOVAL.get()) {
+            bindAndInitFirstWorkspaceScreen();
+        }
+
         // Remove any deferred refresh callbacks
         mLauncher.mHandler.removeCallbacksAndMessages(DeferredWidgetRefresh.class);
 
-        // Ensure that the first page is always present
-        bindAndInitFirstWorkspaceScreen();
-
         // Re-enable the layout transitions
         enableLayoutTransitions();
     }
@@ -799,6 +806,13 @@
         // and we store them as extra empty screens.
         for (int i = 0; i < finalScreens.size(); i++) {
             int screenId = finalScreens.keyAt(i);
+
+            // We don't want to remove the first screen even if it's empty because that's where
+            // first page pinned item would go if it gets turned back on.
+            if (ENABLE_SMARTSPACE_REMOVAL.get() && screenId == FIRST_SCREEN_ID) {
+                continue;
+            }
+
             CellLayout screen = finalScreens.get(screenId);
 
             mWorkspaceScreens.remove(screenId);
@@ -1012,7 +1026,9 @@
             int id = mWorkspaceScreens.keyAt(i);
             CellLayout cl = mWorkspaceScreens.valueAt(i);
             // FIRST_SCREEN_ID can never be removed.
-            if ((!FeatureFlags.QSB_ON_FIRST_SCREEN || id > FIRST_SCREEN_ID)
+            if (((!FeatureFlags.QSB_ON_FIRST_SCREEN
+                    || shouldShowFirstPageWidget())
+                    || id > FIRST_SCREEN_ID)
                     && cl.getShortcutsAndWidgets().getChildCount() == 0) {
                 removeScreens.add(id);
             }
@@ -2858,6 +2874,10 @@
                     view = FolderIcon.inflateFolderAndIcon(R.layout.folder_icon, mLauncher, cellLayout,
                             (FolderInfo) info);
                     break;
+                case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR:
+                    view = AppPairIcon.inflateIcon(R.layout.app_pair_icon, mLauncher, cellLayout,
+                            (FolderInfo) info);
+                    break;
                 default:
                     throw new IllegalStateException("Unknown item type: " + info.itemType);
             }
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 001fd95..b0f13ef 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.allapps;
 
 import static com.android.launcher3.config.FeatureFlags.ALL_APPS_GONE_VISIBILITY;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_RV_PREINFLATION;
 import static com.android.launcher3.logger.LauncherAtom.ContainerInfo;
 import static com.android.launcher3.logger.LauncherAtom.SearchResultContainer;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_PERSONAL_SCROLLED_DOWN;
@@ -100,16 +101,17 @@
     void updatePoolSize(boolean hasWorkProfile) {
         DeviceProfile grid = ActivityContext.lookupContext(getContext()).getDeviceProfile();
         RecyclerView.RecycledViewPool pool = getRecycledViewPool();
-        int approxRows = (int) Math.ceil(grid.availableHeightPx / grid.allAppsIconSizePx);
         pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH, 1);
         pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_ALL_APPS_DIVIDER, 1);
 
-        // If all apps' hidden visibility is INVISIBLE, we will need to preinflate one page of
-        // all apps icons for smooth scrolling.
-        int maxPoolSizeForAppIcons = (approxRows + 1) * grid.numShownAllAppsColumns;
-        if (ALL_APPS_GONE_VISIBILITY.get()) {
-            // If all apps' hidden visibility is GONE, we need to increase prefinated icons number
-            // by [PREINFLATE_ICONS_ROW_COUNT] rows + [EXTRA_ICONS_COUNT] for fast opening all apps.
+        // By default the max num of pool size for app icons is num of app icons in one page of
+        // all apps.
+        int maxPoolSizeForAppIcons = grid.getMaxAllAppsRowCount()
+                * grid.numShownAllAppsColumns;
+        if (ALL_APPS_GONE_VISIBILITY.get() && ENABLE_ALL_APPS_RV_PREINFLATION.get()) {
+            // If we set all apps' hidden visibility to GONE and enable pre-inflation, we want to
+            // preinflate one page of all apps icons plus [PREINFLATE_ICONS_ROW_COUNT] rows +
+            // [EXTRA_ICONS_COUNT]. Thus we need to bump the max pool size of app icons accordingly.
             maxPoolSizeForAppIcons +=
                     PREINFLATE_ICONS_ROW_COUNT * grid.numShownAllAppsColumns + EXTRA_ICONS_COUNT;
         }
diff --git a/src/com/android/launcher3/allapps/DiscoveryBounce.java b/src/com/android/launcher3/allapps/DiscoveryBounce.java
index df22425..1692912 100644
--- a/src/com/android/launcher3/allapps/DiscoveryBounce.java
+++ b/src/com/android/launcher3/allapps/DiscoveryBounce.java
@@ -17,6 +17,7 @@
 package com.android.launcher3.allapps;
 
 import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.util.OnboardingPrefs.HOME_BOUNCE_SEEN;
 
 import android.animation.Animator;
 import android.animation.AnimatorInflater;
@@ -122,9 +123,8 @@
     }
 
     private static void showForHomeIfNeeded(Launcher launcher, boolean withDelay) {
-        OnboardingPrefs onboardingPrefs = launcher.getOnboardingPrefs();
         if (!launcher.isInState(NORMAL)
-                || onboardingPrefs.getBoolean(OnboardingPrefs.HOME_BOUNCE_SEEN)
+                || HOME_BOUNCE_SEEN.get(launcher)
                 || AbstractFloatingView.getTopOpenView(launcher) != null
                 || launcher.getSystemService(UserManager.class).isDemoUser()
                 || Utilities.isRunningInTestHarness()) {
@@ -135,7 +135,7 @@
             new Handler().postDelayed(() -> showForHomeIfNeeded(launcher, false), DELAY_MS);
             return;
         }
-        onboardingPrefs.incrementEventCount(OnboardingPrefs.HOME_BOUNCE_COUNT);
+        OnboardingPrefs.HOME_BOUNCE_COUNT.increment(launcher);
         new DiscoveryBounce(launcher).show();
     }
 
diff --git a/src/com/android/launcher3/allapps/WorkProfileManager.java b/src/com/android/launcher3/allapps/WorkProfileManager.java
index 05ed9ba..ac0e5a4 100644
--- a/src/com/android/launcher3/allapps/WorkProfileManager.java
+++ b/src/com/android/launcher3/allapps/WorkProfileManager.java
@@ -62,8 +62,6 @@
 public class WorkProfileManager implements PersonalWorkSlidingTabStrip.OnActivePageChangedListener {
     private static final String TAG = "WorkProfileManager";
 
-    public static final String KEY_WORK_EDU_STEP = "showed_work_profile_edu";
-
     public static final int STATE_ENABLED = 1;
     public static final int STATE_DISABLED = 2;
     public static final int STATE_TRANSITION = 3;
diff --git a/src/com/android/launcher3/anim/PendingAnimation.java b/src/com/android/launcher3/anim/PendingAnimation.java
index 7316420..fd731f4 100644
--- a/src/com/android/launcher3/anim/PendingAnimation.java
+++ b/src/com/android/launcher3/anim/PendingAnimation.java
@@ -22,8 +22,10 @@
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
+import android.os.Trace;
 import android.util.FloatProperty;
 
+import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorPlaybackController.Holder;
 
 import java.util.ArrayList;
@@ -82,6 +84,15 @@
         add(anim);
     }
 
+    /** If trace is enabled, add counter to trace animation progress. */
+    public void logAnimationProgressToTrace(String counterName) {
+        if (Utilities.ATLEAST_Q && Trace.isEnabled()) {
+            super.addOnFrameListener(
+                    animation -> Trace.setCounter(
+                            counterName, (long) (animation.getAnimatedFraction() * 100)));
+        }
+    }
+
     /**
      * Creates and returns the underlying AnimatorSet
      */
diff --git a/src/com/android/launcher3/apppairs/AppPairIcon.java b/src/com/android/launcher3/apppairs/AppPairIcon.java
index 1dc4ad2..8121245 100644
--- a/src/com/android/launcher3/apppairs/AppPairIcon.java
+++ b/src/com/android/launcher3/apppairs/AppPairIcon.java
@@ -17,7 +17,9 @@
 package com.android.launcher3.apppairs;
 
 import android.content.Context;
+import android.graphics.Canvas;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.ViewGroup;
@@ -26,6 +28,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.model.data.FolderInfo;
@@ -37,11 +40,41 @@
 
 /**
  * A {@link android.widget.FrameLayout} used to represent an app pair icon on the workspace.
+ * <br>
+ * The app pair icon is two parallel background rectangles with rounded corners. Icons of the two
+ * member apps are set into these rectangles.
  */
 public class AppPairIcon extends FrameLayout implements DraggableView {
+    /**
+     * Design specs -- the below ratios are in relation to the size of a standard app icon.
+     */
+    private static final float OUTER_PADDING_SCALE = 1 / 30f;
+    private static final float INNER_PADDING_SCALE = 1 / 24f;
+    private static final float MEMBER_ICON_SCALE = 11 / 30f;
+    private static final float CENTER_CHANNEL_SCALE = 1 / 30f;
+    private static final float BIG_RADIUS_SCALE = 1 / 5f;
+    private static final float SMALL_RADIUS_SCALE = 1 / 15f;
+
+    // App pair icons are slightly smaller than regular icons, so we pad the icon by this much on
+    // each side.
+    float mOuterPadding;
+    // Inside of the icon, the two member apps are padded by this much.
+    float mInnerPadding;
+    // The two member apps have icons that are this big (in diameter).
+    float mMemberIconSize;
+    // The size of the center channel.
+    float mCenterChannelSize;
+    // The large outer radius of the background rectangles.
+    float mBigRadius;
+    // The small inner radius of the background rectangles.
+    float mSmallRadius;
+    // The app pairs icon appears differently in portrait and landscape.
+    boolean mIsLandscape;
 
     private ActivityContext mActivity;
+    // A view that holds the app pair's title.
     private BubbleTextView mAppPairName;
+    // The underlying ItemInfo that stores info about the app pair members, etc.
     private FolderInfo mInfo;
 
     public AppPairIcon(Context context, AttributeSet attrs) {
@@ -53,11 +86,11 @@
     }
 
     /**
-     * Builds an AppPairIcon to be added to the Launcher
+     * Builds an AppPairIcon to be added to the Launcher.
      */
     public static AppPairIcon inflateIcon(int resId, ActivityContext activity,
             @Nullable ViewGroup group, FolderInfo appPairInfo) {
-
+        DeviceProfile grid = activity.getDeviceProfile();
         LayoutInflater inflater = (group != null)
                 ? LayoutInflater.from(group.getContext())
                 : activity.getLayoutInflater();
@@ -67,26 +100,114 @@
         Collections.sort(appPairInfo.contents, Comparator.comparingInt(a -> a.rank));
 
         icon.setClipToPadding(false);
-        icon.mAppPairName = icon.findViewById(R.id.app_pair_icon_name);
-
-        // TODO (jeremysim b/274189428): Replace this placeholder icon
-        WorkspaceItemInfo placeholder = new WorkspaceItemInfo();
-        placeholder.newIcon(icon.getContext());
-        icon.mAppPairName.applyFromWorkspaceItem(placeholder);
-
-        icon.mAppPairName.setText(appPairInfo.title);
-
         icon.setTag(appPairInfo);
         icon.setOnClickListener(activity.getItemOnClickListener());
         icon.mInfo = appPairInfo;
         icon.mActivity = activity;
 
+        // Set up app pair title
+        icon.mAppPairName = icon.findViewById(R.id.app_pair_icon_name);
+        icon.mAppPairName.setCompoundDrawablePadding(0);
+        FrameLayout.LayoutParams lp =
+                (FrameLayout.LayoutParams) icon.mAppPairName.getLayoutParams();
+        lp.topMargin = grid.iconSizePx + grid.iconDrawablePaddingPx;
+        icon.mAppPairName.setText(appPairInfo.title);
+
+        // Set up accessibility
+        icon.setContentDescription(icon.getAccessibilityTitle(
+                appPairInfo.contents.get(0).title, appPairInfo.contents.get(1).title));
         icon.setAccessibilityDelegate(activity.getAccessibilityDelegate());
 
         return icon;
     }
 
     @Override
+    protected void dispatchDraw(Canvas canvas) {
+        super.dispatchDraw(canvas);
+
+        // Calculate device-specific measurements
+        DeviceProfile grid = mActivity.getDeviceProfile();
+        int defaultIconSize = grid.iconSizePx;
+        mOuterPadding = OUTER_PADDING_SCALE * defaultIconSize;
+        mInnerPadding = INNER_PADDING_SCALE * defaultIconSize;
+        mMemberIconSize = MEMBER_ICON_SCALE * defaultIconSize;
+        mCenterChannelSize = CENTER_CHANNEL_SCALE * defaultIconSize;
+        mBigRadius = BIG_RADIUS_SCALE * defaultIconSize;
+        mSmallRadius = SMALL_RADIUS_SCALE * defaultIconSize;
+        mIsLandscape = grid.isLandscape;
+
+        // Calculate drawable area position
+        float leftBound = (canvas.getWidth() / 2f) - (defaultIconSize / 2f);
+        float topBound = getPaddingTop();
+
+        // Prepare to draw app pair icon background
+        Drawable background = new AppPairIconBackground(getContext(), this);
+        background.setBounds(0, 0, defaultIconSize, defaultIconSize);
+
+        // Draw background
+        canvas.save();
+        canvas.translate(leftBound, topBound);
+        background.draw(canvas);
+        canvas.restore();
+
+        // Prepare to draw icons
+        WorkspaceItemInfo app1 = mInfo.contents.get(0);
+        WorkspaceItemInfo app2 = mInfo.contents.get(1);
+        Drawable app1Icon = app1.newIcon(getContext());
+        Drawable app2Icon = app2.newIcon(getContext());
+        app1Icon.setBounds(0, 0, defaultIconSize, defaultIconSize);
+        app2Icon.setBounds(0, 0, defaultIconSize, defaultIconSize);
+
+        // Draw first icon
+        canvas.save();
+        canvas.translate(leftBound, topBound);
+        // The app icons are placed differently depending on device orientation.
+        if (mIsLandscape) {
+            canvas.translate(
+                    (defaultIconSize / 2f) - (mCenterChannelSize / 2f) - mInnerPadding
+                            - mMemberIconSize,
+                    (defaultIconSize / 2f) - (mMemberIconSize / 2f)
+            );
+        } else {
+            canvas.translate(
+                    (defaultIconSize / 2f) - (mMemberIconSize / 2f),
+                    (defaultIconSize / 2f) - (mCenterChannelSize / 2f) - mInnerPadding
+                            - mMemberIconSize
+            );
+
+        }
+        canvas.scale(MEMBER_ICON_SCALE, MEMBER_ICON_SCALE);
+        app1Icon.draw(canvas);
+        canvas.restore();
+
+        // Draw second icon
+        canvas.save();
+        canvas.translate(leftBound, topBound);
+        // The app icons are placed differently depending on device orientation.
+        if (mIsLandscape) {
+            canvas.translate(
+                    (defaultIconSize / 2f) + (mCenterChannelSize / 2f) + mInnerPadding,
+                    (defaultIconSize / 2f) - (mMemberIconSize / 2f)
+            );
+        } else {
+            canvas.translate(
+                    (defaultIconSize / 2f) - (mMemberIconSize / 2f),
+                    (defaultIconSize / 2f) + (mCenterChannelSize / 2f) + mInnerPadding
+            );
+        }
+        canvas.scale(MEMBER_ICON_SCALE, MEMBER_ICON_SCALE);
+        app2Icon.draw(canvas);
+        canvas.restore();
+    }
+
+    /**
+     * Returns a formatted accessibility title for app pairs.
+     */
+    public String getAccessibilityTitle(CharSequence app1, CharSequence app2) {
+        return getContext().getString(R.string.app_pair_name_format, app1, app2);
+    }
+
+    @Override
     public int getViewType() {
         return DRAGGABLE_ICON;
     }
diff --git a/src/com/android/launcher3/apppairs/AppPairIconBackground.java b/src/com/android/launcher3/apppairs/AppPairIconBackground.java
new file mode 100644
index 0000000..735c82f
--- /dev/null
+++ b/src/com/android/launcher3/apppairs/AppPairIconBackground.java
@@ -0,0 +1,167 @@
+/*
+ * 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.apppairs;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+
+import com.android.launcher3.R;
+
+/**
+ * A Drawable for the background behind the twin app icons (looks like two rectangles).
+ */
+class AppPairIconBackground extends Drawable {
+    // The icon that we will draw this background on.
+    private final AppPairIcon icon;
+    private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+
+    /**
+     * Null values to use with
+     * {@link Canvas#drawDoubleRoundRect(RectF, float[], RectF, float[], Paint)}, since there
+     * doesn't seem to be any other API for drawing rectangles with 4 different corner radii.
+     */
+    private static final RectF EMPTY_RECT = new RectF();
+    private static final float[] ARRAY_OF_ZEROES = new float[8];
+
+    AppPairIconBackground(Context context, AppPairIcon appPairIcon) {
+        icon = appPairIcon;
+        // Set up background paint color
+        TypedArray ta = context.getTheme().obtainStyledAttributes(R.styleable.FolderIconPreview);
+        mBackgroundPaint.setStyle(Paint.Style.FILL);
+        mBackgroundPaint.setColor(
+                ta.getColor(R.styleable.FolderIconPreview_folderPreviewColor, 0));
+        ta.recycle();
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        if (icon.mIsLandscape) {
+            drawLeftRightSplit(canvas);
+        } else {
+            drawTopBottomSplit(canvas);
+        }
+    }
+
+    /**
+     * When device is in landscape, we draw the rectangles with a left-right split.
+     */
+    private void drawLeftRightSplit(Canvas canvas) {
+        // Get the bounds where we will draw the background image
+        int width = getBounds().width();
+        int height = getBounds().height();
+
+        // The left half of the background image, excluding center channel
+        RectF leftSide = new RectF(
+                icon.mOuterPadding,
+                icon.mOuterPadding,
+                (width / 2f) - (icon.mCenterChannelSize / 2f),
+                height - icon.mOuterPadding
+        );
+        // The right half of the background image, excluding center channel
+        RectF rightSide = new RectF(
+                (width / 2f) + (icon.mCenterChannelSize / 2f),
+                icon.mOuterPadding,
+                width - icon.mOuterPadding,
+                height - icon.mOuterPadding
+        );
+
+        drawCustomRoundedRect(canvas, leftSide, new float[]{
+                icon.mBigRadius, icon.mBigRadius,
+                icon.mSmallRadius, icon.mSmallRadius,
+                icon.mSmallRadius, icon.mSmallRadius,
+                icon.mBigRadius, icon.mBigRadius});
+        drawCustomRoundedRect(canvas, rightSide, new float[]{
+                icon.mSmallRadius, icon.mSmallRadius,
+                icon.mBigRadius, icon.mBigRadius,
+                icon.mBigRadius, icon.mBigRadius,
+                icon.mSmallRadius, icon.mSmallRadius});
+    }
+
+    /**
+     * When device is in portrait, we draw the rectangles with a top-bottom split.
+     */
+    private void drawTopBottomSplit(Canvas canvas) {
+        // Get the bounds where we will draw the background image
+        int width = getBounds().width();
+        int height = getBounds().height();
+
+        // The top half of the background image, excluding center channel
+        RectF topSide = new RectF(
+                icon.mOuterPadding,
+                icon.mOuterPadding,
+                width - icon.mOuterPadding,
+                (height / 2f) - (icon.mCenterChannelSize / 2f)
+        );
+        // The bottom half of the background image, excluding center channel
+        RectF bottomSide = new RectF(
+                icon.mOuterPadding,
+                (height / 2f) + (icon.mCenterChannelSize / 2f),
+                width - icon.mOuterPadding,
+                height - icon.mOuterPadding
+        );
+
+        drawCustomRoundedRect(canvas, topSide, new float[]{
+                icon.mBigRadius, icon.mBigRadius,
+                icon.mBigRadius, icon.mBigRadius,
+                icon.mSmallRadius, icon.mSmallRadius,
+                icon.mSmallRadius, icon.mSmallRadius});
+        drawCustomRoundedRect(canvas, bottomSide, new float[]{
+                icon.mSmallRadius, icon.mSmallRadius,
+                icon.mSmallRadius, icon.mSmallRadius,
+                icon.mBigRadius, icon.mBigRadius,
+                icon.mBigRadius, icon.mBigRadius});
+    }
+
+    /**
+     * Draws a rectangle with custom rounded corners.
+     * @param c The Canvas to draw on.
+     * @param rect The bounds of the rectangle.
+     * @param radii An array of 8 radii for the corners: top left x, top left y, top right x, top
+     *              right y, bottom right x, and so on.
+     */
+    private void drawCustomRoundedRect(Canvas c, RectF rect, float[] radii) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+            // Canvas.drawDoubleRoundRect is supported from Q onward
+            c.drawDoubleRoundRect(rect, radii, EMPTY_RECT, ARRAY_OF_ZEROES, mBackgroundPaint);
+        } else {
+            // Fallback rectangle with uniform rounded corners
+            c.drawRoundRect(rect, icon.mBigRadius, icon.mBigRadius, mBackgroundPaint);
+        }
+    }
+
+    @Override
+    public int getOpacity() {
+        return PixelFormat.OPAQUE;
+    }
+
+    @Override
+    public void setAlpha(int i) {
+        // Required by Drawable but not used.
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) {
+        // Required by Drawable but not used.
+    }
+}
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index a3c434a..8610efe 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.config;
 
+import static com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN;
 import static com.android.launcher3.config.FeatureFlags.FlagState.DISABLED;
 import static com.android.launcher3.config.FeatureFlags.FlagState.ENABLED;
 import static com.android.launcher3.config.FeatureFlags.FlagState.TEAMFOOD;
@@ -118,6 +119,10 @@
             getDebugFlag(275132633, "ENABLE_ALL_APPS_FROM_OVERVIEW", DISABLED,
                     "Allow entering All Apps from Overview (e.g. long swipe up from app)");
 
+    public static final BooleanFlag CUSTOM_LPNH_THRESHOLDS =
+            getDebugFlag(301680992, "CUSTOM_LPNH_THRESHOLDS", DISABLED,
+                    "Add dev options to customize the LPNH trigger slop and milliseconds");
+
     public static final BooleanFlag ENABLE_SHOW_KEYBOARD_OPTION_IN_ALL_APPS = getReleaseFlag(
             270394468, "ENABLE_SHOW_KEYBOARD_OPTION_IN_ALL_APPS", ENABLED,
             "Enable option to show keyboard when going to all-apps");
@@ -136,7 +141,7 @@
 
     // TODO(Block 6): Clean up flags
     public static final BooleanFlag ENABLE_ALL_APPS_SEARCH_IN_TASKBAR = getDebugFlag(270393900,
-            "ENABLE_ALL_APPS_SEARCH_IN_TASKBAR", TEAMFOOD,
+            "ENABLE_ALL_APPS_SEARCH_IN_TASKBAR", ENABLED,
             "Enables Search box in Taskbar All Apps.");
 
     public static final BooleanFlag SECONDARY_DRAG_N_DROP_TO_PIN = getDebugFlag(270395140,
@@ -151,21 +156,6 @@
     // TODO(Block 8): Clean up flags
 
     // TODO(Block 9): Clean up flags
-    public static final BooleanFlag ENABLE_DOWNLOAD_APP_UX_V2 = getReleaseFlag(270395134,
-            "ENABLE_DOWNLOAD_APP_UX_V2", ENABLED, "Updates the download app UX"
-                    + " to have better visuals");
-
-    public static final BooleanFlag ENABLE_DOWNLOAD_APP_UX_V3 = getDebugFlag(270395186,
-            "ENABLE_DOWNLOAD_APP_UX_V3", ENABLED, "Updates the download app UX"
-                    + " to have better visuals, improve contrast, and color");
-
-    public static final BooleanFlag SHOW_DOT_PAGINATION = getDebugFlag(270395278,
-            "SHOW_DOT_PAGINATION", ENABLED, "Enable showing dot pagination in workspace");
-
-    public static final BooleanFlag LARGE_SCREEN_WIDGET_PICKER = getDebugFlag(270395809,
-            "LARGE_SCREEN_WIDGET_PICKER", ENABLED, "Enable new widget picker that takes "
-                    + "advantage of large screen format");
-
     public static final BooleanFlag UNFOLDED_WIDGET_PICKER = getDebugFlag(301918659,
             "UNFOLDED_WIDGET_PICKER", DISABLED, "Enable new widget picker that takes "
                     + "advantage of the unfolded foldable format");
@@ -177,6 +167,14 @@
     public static final BooleanFlag SMARTSPACE_AS_A_WIDGET = getDebugFlag(299181941,
             "SMARTSPACE_AS_A_WIDGET", DISABLED, "Enable SmartSpace as a widget");
 
+    public static boolean shouldShowFirstPageWidget() {
+        return SMARTSPACE_AS_A_WIDGET.get() && WIDGET_ON_FIRST_SCREEN;
+    }
+
+    public static final BooleanFlag ENABLE_SMARTSPACE_REMOVAL = getDebugFlag(290799975,
+            "ENABLE_SMARTSPACE_REMOVAL", DISABLED, "Enable SmartSpace removal for "
+            + "home screen");
+
     // TODO(Block 10): Clean up flags
     public static final BooleanFlag ENABLE_BACK_SWIPE_LAUNCHER_ANIMATION = getDebugFlag(270614790,
             "ENABLE_BACK_SWIPE_LAUNCHER_ANIMATION", DISABLED,
@@ -270,16 +268,20 @@
             "Enables taskbar pinning to allow user to switch between transient and persistent "
                     + "taskbar flavors");
 
-    public static final BooleanFlag ENABLE_BOOT_AWARE_STARTUP_DATA = getDebugFlag(251502424,
-            "ENABLE_BOOT_AWARE_STARTUP_DATA", DISABLED, "Marks LauncherPref data as (and allows it "
-                    + "to) available while the device is locked. Enabling this causes a 1-time "
-                    + "migration of certain SharedPreferences data. Improves startup latency.");
+    public static final BooleanFlag MOVE_STARTUP_DATA_TO_DEVICE_PROTECTED_STORAGE = getDebugFlag(
+            251502424, "ENABLE_BOOT_AWARE_STARTUP_DATA", DISABLED,
+            "Marks LauncherPref data as (and allows it to) available while the device is"
+                    + " locked. Enabling this causes a 1-time movement of certain SharedPreferences"
+                    + " data. Improves startup latency.");
 
-    // TODO(Block 18): Clean up flags
+    // Aconfig migration complete for ENABLE_APP_PAIRS.
     public static final BooleanFlag ENABLE_APP_PAIRS = getDebugFlag(274189428,
             "ENABLE_APP_PAIRS", DISABLED,
             "Enables the ability to create and save app pairs on the Home screen for easy"
                     + " split screen launching.");
+    public static boolean enableAppPairs() {
+        return ENABLE_APP_PAIRS.get() || com.android.wm.shell.Flags.enableAppPairs();
+    }
 
     // TODO(Block 19): Clean up flags
     public static final BooleanFlag SCROLL_TOP_TO_RESET = getReleaseFlag(270395177,
@@ -317,16 +319,6 @@
                     + "waiting for SystemUI and then merging the SystemUI progress whenever we "
                     + "start receiving the events");
 
-    // TODO(Block 23): Clean up flags
-    // Aconfig migration complete for ENABLE_GRID_ONLY_OVERVIEW.
-    @VisibleForTesting
-    public static final BooleanFlag ENABLE_GRID_ONLY_OVERVIEW = getDebugFlag(270397206,
-            "ENABLE_GRID_ONLY_OVERVIEW", TEAMFOOD,
-            "Enable a grid-only overview without a focused task.");
-    public static boolean enableGridOnlyOverview() {
-        return ENABLE_GRID_ONLY_OVERVIEW.get() || Flags.enableGridOnlyOverview();
-    }
-
     // Aconfig migration complete for ENABLE_OVERVIEW_ICON_MENU.
     @VisibleForTesting
     public static final BooleanFlag ENABLE_OVERVIEW_ICON_MENU = getDebugFlag(257950105,
@@ -405,10 +397,6 @@
             270393453, "ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE", DISABLED,
             "Enable initiating split screen from workspace to workspace.");
 
-    public static final BooleanFlag ENABLE_SPLIT_FROM_DESKTOP_TO_WORKSPACE = getDebugFlag(
-            279586624, "ENABLE_SPLIT_FROM_DESKTOP_TO_WORKSPACE", DISABLED,
-            "Enable initiating split screen from desktop mode to workspace.");
-
     public static final BooleanFlag ENABLE_TRACKPAD_GESTURE = getDebugFlag(271010401,
             "ENABLE_TRACKPAD_GESTURE", ENABLED, "Enables trackpad gesture.");
 
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index ae44f0a..3330448 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -22,6 +22,7 @@
 
 import static com.android.launcher3.DeviceProfile.DEFAULT_SCALE;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
+import static com.android.launcher3.config.FeatureFlags.shouldShowFirstPageWidget;
 import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
 import static com.android.launcher3.model.ModelUtils.getMissingHotseatRanks;
 
@@ -526,7 +527,8 @@
         }
 
         // Add first page QSB
-        if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
+        if (FeatureFlags.QSB_ON_FIRST_SCREEN && dataModel.isFirstPagePinnedItemEnabled
+                && !shouldShowFirstPageWidget()) {
             CellLayout firstScreen = mWorkspaceScreens.get(FIRST_SCREEN_ID);
             View qsb = mHomeElementInflater.inflate(R.layout.qsb_preview, firstScreen, false);
             CellLayoutLayoutParams lp = new CellLayoutLayoutParams(
diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
index 307052a..3e77c78 100644
--- a/src/com/android/launcher3/graphics/PreloadIconDrawable.java
+++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
@@ -19,8 +19,6 @@
 
 import static com.android.app.animation.Interpolators.EMPHASIZED;
 import static com.android.app.animation.Interpolators.LINEAR;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_DOWNLOAD_APP_UX_V2;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_DOWNLOAD_APP_UX_V3;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -76,10 +74,8 @@
     // Duration = COMPLETE_ANIM_FRACTION * DURATION_SCALE
     private static final float COMPLETE_ANIM_FRACTION = 1f;
 
-    private static final float SMALL_SCALE = ENABLE_DOWNLOAD_APP_UX_V3.get() ? 0.8f : 0.7f;
-    private static final float PROGRESS_STROKE_SCALE = ENABLE_DOWNLOAD_APP_UX_V2.get()
-            ? 0.055f
-            : 0.075f;
+    private static final float SMALL_SCALE = 0.8f;
+    private static final float PROGRESS_STROKE_SCALE = 0.055f;
     private static final float PROGRESS_BOUNDS_SCALE = 0.075f;
     private static final int PRELOAD_ACCENT_COLOR_INDEX = 0;
     private static final int PRELOAD_BACKGROUND_COLOR_INDEX = 1;
@@ -119,8 +115,6 @@
 
     private ObjectAnimator mCurrentAnim;
 
-    private boolean mIsStartable;
-
     public PreloadIconDrawable(ItemInfoWithIcon info, Context context) {
         this(
                 info,
@@ -144,9 +138,7 @@
 
         mProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
         mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
-        if (ENABLE_DOWNLOAD_APP_UX_V3.get()) {
-            mProgressPaint.setAlpha(MAX_PAINT_ALPHA);
-        }
+        mProgressPaint.setAlpha(MAX_PAINT_ALPHA);
         mIndicatorColor = indicatorColor;
 
         // This is the color
@@ -181,9 +173,6 @@
         mIconScaleMultiplier.updateValue(info.getProgressLevel() == 0 ? 0 : 1);
 
         setLevel(info.getProgressLevel());
-        if (!ENABLE_DOWNLOAD_APP_UX_V2.get()) {
-            setIsStartable(info.isAppStartable());
-        }
     }
 
     @Override
@@ -212,54 +201,31 @@
             return;
         }
 
-        if (mInternalStateProgress > 0
-                && (ENABLE_DOWNLOAD_APP_UX_V3.get() || !ENABLE_DOWNLOAD_APP_UX_V2.get())) {
+        if (mInternalStateProgress > 0) {
             // Draw background.
-            mProgressPaint.setStyle(ENABLE_DOWNLOAD_APP_UX_V3.get()
-                    ? Paint.Style.FILL
-                    : Paint.Style.FILL_AND_STROKE);
-            mProgressPaint.setColor(ENABLE_DOWNLOAD_APP_UX_V3.get()
-                    ? mPlateColor
-                    : mSystemBackgroundColor);
+            mProgressPaint.setStyle(Paint.Style.FILL);
+            mProgressPaint.setColor(mPlateColor);
             canvas.drawPath(mScaledTrackPath, mProgressPaint);
         }
 
-        if (!ENABLE_DOWNLOAD_APP_UX_V2.get() || mInternalStateProgress > 0) {
+        if (mInternalStateProgress > 0) {
             // Draw track and progress.
             mProgressPaint.setStyle(Paint.Style.STROKE);
-            mProgressPaint.setColor(ENABLE_DOWNLOAD_APP_UX_V3.get()
-                    ? mTrackColor
-                    : mSystemAccentColor);
-            if (!ENABLE_DOWNLOAD_APP_UX_V3.get()) {
-                mProgressPaint.setAlpha(TRACK_ALPHA);
-            }
+            mProgressPaint.setColor(mTrackColor);
             canvas.drawPath(mScaledTrackPath, mProgressPaint);
             mProgressPaint.setAlpha(MAX_PAINT_ALPHA);
-            if (ENABLE_DOWNLOAD_APP_UX_V3.get()) {
-                mProgressPaint.setColor(mProgressColor);
-            }
+            mProgressPaint.setColor(mProgressColor);
             canvas.drawPath(mScaledProgressPath, mProgressPaint);
         }
 
         int saveCount = canvas.save();
-        float scale = ENABLE_DOWNLOAD_APP_UX_V2.get()
-                ? 1 - mIconScaleMultiplier.value * (1 - SMALL_SCALE)
-                : SMALL_SCALE;
+        float scale = 1 - mIconScaleMultiplier.value * (1 - SMALL_SCALE);
         canvas.scale(scale, scale, bounds.exactCenterX(), bounds.exactCenterY());
 
         super.drawInternal(canvas, bounds);
         canvas.restoreToCount(saveCount);
     }
 
-    @Override
-    protected void updateFilter() {
-        if (!ENABLE_DOWNLOAD_APP_UX_V2.get()) {
-            setAlpha(mIsDisabled ? DISABLED_ICON_ALPHA : MAX_PAINT_ALPHA);
-        } else {
-            super.updateFilter();
-        }
-    }
-
     /**
      * Updates the install progress based on the level
      */
@@ -296,14 +262,6 @@
         return !mRanFinishAnimation;
     }
 
-    /** Sets whether this icon should display the startable app UI. */
-    public void setIsStartable(boolean isStartable) {
-        if (mIsStartable != isStartable) {
-            mIsStartable = isStartable;
-            setIsDisabled(!isStartable);
-        }
-    }
-
     private void updateInternalState(
             float finalProgress, boolean isFinish, Runnable onFinishCallback) {
         if (mCurrentAnim != null) {
@@ -355,7 +313,7 @@
      */
     private void setInternalProgress(float progress) {
         // Animate scale and alpha from pending to downloading state.
-        if (ENABLE_DOWNLOAD_APP_UX_V2.get() && progress > 0 && mInternalStateProgress == 0) {
+        if (progress > 0 && mInternalStateProgress == 0) {
             // Progress is changing for the first time, animate the icon scale
             Animator iconScaleAnimator = mIconScaleMultiplier.animateToValue(1);
             iconScaleAnimator.setDuration(SCALE_AND_ALPHA_ANIM_DURATION);
@@ -365,14 +323,11 @@
 
         mInternalStateProgress = progress;
         if (progress <= 0) {
-            if (!ENABLE_DOWNLOAD_APP_UX_V2.get()) {
-                mScaledTrackPath.reset();
-            }
             mIconScaleMultiplier.updateValue(0);
         } else {
             mPathMeasure.getSegment(
                     0, Math.min(progress, 1) * mTrackLength, mScaledProgressPath, true);
-            if (progress > 1 && ENABLE_DOWNLOAD_APP_UX_V2.get()) {
+            if (progress > 1) {
                 // map the scale back to original value
                 mIconScaleMultiplier.updateValue(Utilities.mapBoundToRange(
                         progress - 1, 0, COMPLETE_ANIM_FRACTION, 1, 0, EMPHASIZED));
diff --git a/src/com/android/launcher3/icons/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java
index 57fa8a2..a15348b 100644
--- a/src/com/android/launcher3/icons/LauncherIcons.java
+++ b/src/com/android/launcher3/icons/LauncherIcons.java
@@ -20,11 +20,16 @@
 
 import android.content.Context;
 import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+
+import androidx.annotation.NonNull;
 
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.graphics.IconShape;
 import com.android.launcher3.graphics.LauncherPreviewRenderer;
+import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.util.Themes;
+import com.android.launcher3.util.UserIconInfo;
 
 /**
  * Wrapper class to provide access to {@link BaseIconFactory} and also to provide pool of this class
@@ -107,6 +112,12 @@
         return mMonochromeIconFactory.wrap(base);
     }
 
+    @NonNull
+    @Override
+    protected UserIconInfo getUserInfo(@NonNull UserHandle user) {
+        return UserCache.INSTANCE.get(mContext).getUserInfo(user);
+    }
+
     @Override
     public void close() {
         recycle();
diff --git a/src/com/android/launcher3/icons/ShortcutCachingLogic.java b/src/com/android/launcher3/icons/ShortcutCachingLogic.java
index bb7248f..1791539 100644
--- a/src/com/android/launcher3/icons/ShortcutCachingLogic.java
+++ b/src/com/android/launcher3/icons/ShortcutCachingLogic.java
@@ -107,7 +107,7 @@
         try {
             return context.getSystemService(LauncherApps.class)
                     .getShortcutIconDrawable(shortcutInfo, density);
-        } catch (SecurityException | IllegalStateException e) {
+        } catch (SecurityException | IllegalStateException | NullPointerException e) {
             Log.e(TAG, "Failed to get shortcut icon", e);
             return null;
         }
diff --git a/src/com/android/launcher3/logging/StartupLatencyLogger.kt b/src/com/android/launcher3/logging/StartupLatencyLogger.kt
index 93e9de5..7d7564b 100644
--- a/src/com/android/launcher3/logging/StartupLatencyLogger.kt
+++ b/src/com/android/launcher3/logging/StartupLatencyLogger.kt
@@ -30,6 +30,11 @@
     @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
     var workspaceLoadStartTime: Long = UNSET_LONG
 
+    // StartupLatencyLogger should only send launcher startup logs once in each launcher activity
+    // lifecycle. After launcher activity startup is completed, the logger should be torn down and
+    // reject all logging calls. This flag should be checked at all APIs to prevent logging invalid
+    // startup metrics (such as loading workspace in screen rotation).
+    var isTornDown = false
     private var isInTest = false
 
     /** Subclass can override this method to handle collected latency metrics. */
@@ -45,6 +50,9 @@
     @MainThread
     fun logWorkspaceLoadStartTime(startTimeMs: Long): StartupLatencyLogger {
         Preconditions.assertUIThread()
+        if (isTornDown) {
+            return this
+        }
         workspaceLoadStartTime = startTimeMs
         return this
     }
@@ -56,6 +64,9 @@
     @MainThread
     fun logCardinality(cardinality: Int): StartupLatencyLogger {
         Preconditions.assertUIThread()
+        if (isTornDown) {
+            return this
+        }
         this.cardinality = cardinality
         return this
     }
@@ -67,6 +78,9 @@
     fun logStart(event: LauncherLatencyEvent, startTimeMs: Long): StartupLatencyLogger {
         // In unit test no looper is attached to current thread
         Preconditions.assertUIThread()
+        if (isTornDown) {
+            return this
+        }
         if (validateLoggingEventAtStart(event)) {
             startTimeByEvent.put(event.id, startTimeMs)
         }
@@ -80,6 +94,9 @@
     fun logEnd(event: LauncherLatencyEvent, endTimeMs: Long): StartupLatencyLogger {
         // In unit test no looper is attached to current thread
         Preconditions.assertUIThread()
+        if (isTornDown) {
+            return this
+        }
         maybeLogStartOfWorkspaceLoadTime(event)
         if (validateLoggingEventAtEnd(event)) {
             endTimeByEvent.put(event.id, endTimeMs)
@@ -96,6 +113,7 @@
         endTimeByEvent.clear()
         cardinality = UNSET_INT
         workspaceLoadStartTime = UNSET_LONG
+        isTornDown = true
     }
 
     @MainThread
@@ -181,6 +199,15 @@
                 "Cannot end ${event.name} event after ${LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION.name}",
             )
             return false
+        } else if (
+            latencyType == LatencyType.COLD_DEVICE_REBOOTING &&
+                event == LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_SYNC
+        ) {
+            Log.e(
+                TAG,
+                "Cannot have ${LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_SYNC.name} in ${LatencyType.COLD_DEVICE_REBOOTING.name} startup type"
+            )
+            return false
         }
         return true
     }
diff --git a/src/com/android/launcher3/model/BaseLauncherBinder.java b/src/com/android/launcher3/model/BaseLauncherBinder.java
index 68544cf..9b2344d 100644
--- a/src/com/android/launcher3/model/BaseLauncherBinder.java
+++ b/src/com/android/launcher3/model/BaseLauncherBinder.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.model;
 
+import static com.android.launcher3.config.FeatureFlags.ENABLE_SMARTSPACE_REMOVAL;
 import static com.android.launcher3.model.ItemInstallQueue.FLAG_LOADER_RUNNING;
 import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -172,6 +173,11 @@
     public abstract void bindWidgets();
 
     /**
+     * bindWidgets is abstract because it is a no-op for the go launcher.
+     */
+    public abstract void bindSmartspaceWidget();
+
+    /**
      * Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to right)
      */
     protected void sortWorkspaceItemsSpatially(InvariantDeviceProfile profile,
@@ -288,6 +294,10 @@
             executeCallbacksTask(c -> {
                 c.clearPendingBinds();
                 c.startBinding();
+                if (ENABLE_SMARTSPACE_REMOVAL.get()) {
+                    c.setIsFirstPagePinnedItemEnabled(
+                            mBgDataModel.isFirstPagePinnedItemEnabled);
+                }
             }, mUiExecutor);
 
             // Bind workspace screens
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 5e88b9b..54ecc00 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -17,6 +17,9 @@
 
 import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY;
 
+import static com.android.launcher3.BuildConfig.QSB_ON_FIRST_SCREEN;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_SMARTSPACE_REMOVAL;
+import static com.android.launcher3.config.FeatureFlags.shouldShowFirstPageWidget;
 import static com.android.launcher3.model.WidgetsModel.GO_DISABLE_WIDGETS;
 import static com.android.launcher3.shortcuts.ShortcutRequest.PINNED;
 
@@ -129,6 +132,8 @@
      * Load id for which the callbacks were successfully bound
      */
     public int lastLoadId = -1;
+    public boolean isFirstPagePinnedItemEnabled = QSB_ON_FIRST_SCREEN
+            && !ENABLE_SMARTSPACE_REMOVAL.get();
 
     /**
      * Clears all the data
@@ -152,7 +157,9 @@
                 screenSet.add(item.screenId);
             }
         }
-        if (FeatureFlags.QSB_ON_FIRST_SCREEN || screenSet.isEmpty()) {
+        if ((FeatureFlags.QSB_ON_FIRST_SCREEN
+                && !shouldShowFirstPageWidget())
+                || screenSet.isEmpty()) {
             screenSet.add(Workspace.FIRST_SCREEN_ID);
         }
         return screenSet.getArray();
@@ -486,6 +493,7 @@
 
         default void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons) { }
         default void bindScreens(IntArray orderedScreenIds) { }
+        default void setIsFirstPagePinnedItemEnabled(boolean isFirstPagePinnedItemEnabled) { }
         default void finishBindingItems(IntSet pagesBoundFirst) { }
         default void preAddApps() { }
         default void bindAppsAdded(IntArray newScreens,
@@ -505,6 +513,7 @@
         default void bindRestoreItemsChange(HashSet<ItemInfo> updates) { }
         default void bindWorkspaceComponentsRemoved(Predicate<ItemInfo> matcher) { }
         default void bindAllWidgets(List<WidgetsListBaseEntry> widgets) { }
+        default void bindSmartspaceWidget() { }
 
         /** Called when workspace has been bound. */
         default void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks,
diff --git a/src/com/android/launcher3/model/DatabaseHelper.java b/src/com/android/launcher3/model/DatabaseHelper.java
index ecf5f67..8167b97 100644
--- a/src/com/android/launcher3/model/DatabaseHelper.java
+++ b/src/com/android/launcher3/model/DatabaseHelper.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.model;
 
 import static com.android.launcher3.LauncherSettings.Favorites.addTableToDb;
+import static com.android.launcher3.config.FeatureFlags.shouldShowFirstPageWidget;
 import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
 
 import android.content.ContentValues;
@@ -257,7 +258,8 @@
                         Favorites.SCREEN, IntArray.wrap(-777, -778)), null);
             }
             case 30: {
-                if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
+                if (FeatureFlags.QSB_ON_FIRST_SCREEN
+                        && !shouldShowFirstPageWidget()) {
                     // Clean up first row in screen 0 as it might contain junk data.
                     Log.d(TAG, "Cleaning up first row");
                     db.delete(Favorites.TABLE_NAME,
diff --git a/src/com/android/launcher3/model/GridSizeMigrationUtil.java b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
index 78875a3..efd5574 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationUtil.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
@@ -18,6 +18,9 @@
 
 import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
 import static com.android.launcher3.LauncherSettings.Favorites.TMP_TABLE;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_SMARTSPACE_REMOVAL;
+import static com.android.launcher3.config.FeatureFlags.shouldShowFirstPageWidget;
+import static com.android.launcher3.model.LoaderTask.SMARTSPACE_ON_HOME_SCREEN;
 import static com.android.launcher3.provider.LauncherDbUtils.copyTable;
 import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
 
@@ -37,6 +40,7 @@
 import androidx.annotation.NonNull;
 
 import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
@@ -327,7 +331,11 @@
             @NonNull final List<DbEntry> sortedItemsToPlace, final boolean matchingScreenIdOnly) {
         final GridOccupancy occupied = new GridOccupancy(trgX, trgY);
         final Point trg = new Point(trgX, trgY);
-        final Point next = new Point(0, screenId == 0 && FeatureFlags.QSB_ON_FIRST_SCREEN
+        final Point next = new Point(0, screenId == 0
+                && (FeatureFlags.QSB_ON_FIRST_SCREEN
+                && (!ENABLE_SMARTSPACE_REMOVAL.get() || LauncherPrefs.getPrefs(destReader.mContext)
+                .getBoolean(SMARTSPACE_ON_HOME_SCREEN, true))
+                && !shouldShowFirstPageWidget())
                 ? 1 /* smartspace */ : 0);
         List<DbEntry> existedEntries = destReader.mWorkspaceEntriesByScreenId.get(screenId);
         if (existedEntries != null) {
@@ -465,6 +473,13 @@
                             }
                             break;
                         }
+                        case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR: {
+                            int total = getFolderItemsCount(entry);
+                            if (total != 2) {
+                                throw new Exception("App pair contains fewer or more than 2 items");
+                            }
+                            break;
+                        }
                         default:
                             throw new Exception("Invalid item type");
                     }
@@ -562,6 +577,13 @@
                             }
                             break;
                         }
+                        case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR: {
+                            int total = getFolderItemsCount(entry);
+                            if (total != 2) {
+                                throw new Exception("App pair contains fewer or more than 2 items");
+                            }
+                            break;
+                        }
                         default:
                             throw new Exception("Invalid item type");
                     }
@@ -679,6 +701,7 @@
         public String getEntryMigrationId() {
             switch (itemType) {
                 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+                case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR:
                     return getFolderMigrationId();
                 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
                     return mProvider;
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 33332f0..4370043 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -17,6 +17,7 @@
 package com.android.launcher3.model;
 
 import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
+import static com.android.launcher3.config.FeatureFlags.shouldShowFirstPageWidget;
 
 import android.content.ComponentName;
 import android.content.ContentValues;
@@ -470,7 +471,7 @@
             // cause the item loading to get skipped
             ShortcutKey.fromItemInfo(info);
         }
-        if (checkItemPlacement(info)) {
+        if (checkItemPlacement(info, dataModel.isFirstPagePinnedItemEnabled)) {
             dataModel.addItem(mContext, info, false, logger);
         } else {
             markDeleted("Item position overlap");
@@ -480,7 +481,7 @@
     /**
      * check & update map of what's occupied; used to discard overlapping/invalid items
      */
-    protected boolean checkItemPlacement(ItemInfo item) {
+    protected boolean checkItemPlacement(ItemInfo item, boolean isFirstPagePinnedItemEnabled) {
         int containerIndex = item.screenId;
         if (item.container == Favorites.CONTAINER_HOTSEAT) {
             final GridOccupancy hotseatOccupancy =
@@ -528,7 +529,8 @@
 
         if (!mOccupied.containsKey(item.screenId)) {
             GridOccupancy screen = new GridOccupancy(countX + 1, countY + 1);
-            if (item.screenId == Workspace.FIRST_SCREEN_ID && FeatureFlags.QSB_ON_FIRST_SCREEN) {
+            if (item.screenId == Workspace.FIRST_SCREEN_ID && (FeatureFlags.QSB_ON_FIRST_SCREEN
+                    && !shouldShowFirstPageWidget() && isFirstPagePinnedItemEnabled)) {
                 // Mark the first X columns (X is width of the search container) in the first row as
                 // occupied (if the feature is enabled) in order to account for the search
                 // container.
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 08a6cf0..6ab8fc5 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -16,7 +16,11 @@
 
 package com.android.launcher3.model;
 
+import static com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN;
+import static com.android.launcher3.LauncherPrefs.SHOULD_SHOW_SMARTSPACE;
 import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_SMARTSPACE_REMOVAL;
+import static com.android.launcher3.config.FeatureFlags.SMARTSPACE_AS_A_WIDGET;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
@@ -57,6 +61,7 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
@@ -115,6 +120,7 @@
  */
 public class LoaderTask implements Runnable {
     private static final String TAG = "LoaderTask";
+    public static final String SMARTSPACE_ON_HOME_SCREEN = "pref_smartspace_home_screen";
 
     private static final boolean DEBUG = true;
 
@@ -296,6 +302,19 @@
             logASplit("bindWidgets");
             verifyNotStopped();
 
+            LauncherPrefs prefs = LauncherPrefs.get(mApp.getContext());
+            if (SMARTSPACE_AS_A_WIDGET.get() && prefs.get(SHOULD_SHOW_SMARTSPACE)) {
+                mLauncherBinder.bindSmartspaceWidget();
+                // Turn off pref.
+                prefs.putSync(SHOULD_SHOW_SMARTSPACE.to(false));
+                logASplit("bindSmartspaceWidget");
+                verifyNotStopped();
+            } else if (!SMARTSPACE_AS_A_WIDGET.get() && WIDGET_ON_FIRST_SCREEN
+                    && !prefs.get(LauncherPrefs.SHOULD_SHOW_SMARTSPACE)) {
+                // Turn on pref.
+                prefs.putSync(SHOULD_SHOW_SMARTSPACE.to(true));
+            }
+
             if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
                 mModelDelegate.loadAndBindOtherItems(mLauncherBinder.mCallbacksList);
                 logASplit("otherDelegateItems");
@@ -351,6 +370,9 @@
             mModelDelegate.markActive();
             logASplit("workspaceDelegateItems");
         }
+        mBgDataModel.isFirstPagePinnedItemEnabled = FeatureFlags.QSB_ON_FIRST_SCREEN
+                && (!ENABLE_SMARTSPACE_REMOVAL.get() || LauncherPrefs.getPrefs(
+                mApp.getContext()).getBoolean(SMARTSPACE_ON_HOME_SCREEN, true));
     }
 
     private void loadWorkspaceImpl(
diff --git a/src/com/android/launcher3/model/ModelDbController.java b/src/com/android/launcher3/model/ModelDbController.java
index e10e72d..139efc3 100644
--- a/src/com/android/launcher3/model/ModelDbController.java
+++ b/src/com/android/launcher3/model/ModelDbController.java
@@ -54,6 +54,7 @@
 import com.android.launcher3.AutoInstallsLayout.SourceResources;
 import com.android.launcher3.ConstantItem;
 import com.android.launcher3.DefaultLayoutParser;
+import com.android.launcher3.EncryptionType;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherFiles;
@@ -499,11 +500,11 @@
     private ConstantItem<Boolean> getEmptyDbCreatedKey(String dbName) {
         if (mContext instanceof SandboxContext) {
             return LauncherPrefs.nonRestorableItem(EMPTY_DATABASE_CREATED,
-                    false /* default value */, false /* boot aware */);
+                    false /* default value */, EncryptionType.ENCRYPTED);
         }
         String key = TextUtils.equals(dbName, LauncherFiles.LAUNCHER_DB)
                 ? EMPTY_DATABASE_CREATED : EMPTY_DATABASE_CREATED + "@" + dbName;
-        return LauncherPrefs.backedUpItem(key, false /* default value */, false /* boot aware */);
+        return LauncherPrefs.backedUpItem(key, false /* default value */, EncryptionType.ENCRYPTED);
     }
 
     /**
diff --git a/src/com/android/launcher3/model/WorkspaceItemSpaceFinder.java b/src/com/android/launcher3/model/WorkspaceItemSpaceFinder.java
index 1fc8a03..929f698 100644
--- a/src/com/android/launcher3/model/WorkspaceItemSpaceFinder.java
+++ b/src/com/android/launcher3/model/WorkspaceItemSpaceFinder.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.model;
 
 import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
+import static com.android.launcher3.config.FeatureFlags.shouldShowFirstPageWidget;
 
 import android.util.LongSparseArray;
 
@@ -66,7 +67,8 @@
         int screenCount = workspaceScreens.size();
         // First check the preferred screen.
         IntSet screensToExclude = new IntSet();
-        if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
+        if (FeatureFlags.QSB_ON_FIRST_SCREEN
+                && !shouldShowFirstPageWidget()) {
             screensToExclude.add(FIRST_SCREEN_ID);
         }
 
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
index ce71275..323b3a7 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
@@ -16,8 +16,6 @@
 
 package com.android.launcher3.pageindicators;
 
-import static com.android.launcher3.config.FeatureFlags.SHOW_DOT_PAGINATION;
-
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
@@ -69,7 +67,7 @@
     private static final int PAGE_INDICATOR_ALPHA = 255;
     private static final int DOT_ALPHA = 128;
     private static final float DOT_ALPHA_FRACTION = 0.5f;
-    private static final int DOT_GAP_FACTOR = SHOW_DOT_PAGINATION.get() ? 4 : 3;
+    private static final int DOT_GAP_FACTOR = 4;
     private static final int VISIBLE_ALPHA = 255;
     private static final int INVISIBLE_ALPHA = 0;
     private Paint mPaginationPaint;
@@ -153,10 +151,7 @@
         mPaginationPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
         mPaginationPaint.setStyle(Style.FILL);
         mPaginationPaint.setColor(Themes.getAttrColor(context, R.attr.folderPaginationColor));
-        mDotRadius = (SHOW_DOT_PAGINATION.get()
-                ? getResources().getDimension(R.dimen.page_indicator_dot_size_v2)
-                : getResources().getDimension(R.dimen.page_indicator_dot_size))
-                / 2;
+        mDotRadius = getResources().getDimension(R.dimen.page_indicator_dot_size) / 2;
         mCircleGap = DOT_GAP_FACTOR * mDotRadius;
         setOutlineProvider(new MyOutlineProver());
         mIsRtl = Utilities.isRtl(getResources());
@@ -164,7 +159,7 @@
 
     @Override
     public void setScroll(int currentScroll, int totalScroll) {
-        if (SHOW_DOT_PAGINATION.get() && currentScroll == 0 && totalScroll == 0) {
+        if (currentScroll == 0 && totalScroll == 0) {
             CURRENT_POSITION.set(this, (float) mActivePage);
             return;
         }
@@ -217,7 +212,7 @@
 
     @Override
     public void setShouldAutoHide(boolean shouldAutoHide) {
-        mShouldAutoHide = shouldAutoHide && SHOW_DOT_PAGINATION.get();
+        mShouldAutoHide = shouldAutoHide;
         if (shouldAutoHide && mPaginationPaint.getAlpha() > INVISIBLE_ALPHA) {
             hideAfterDelay();
         } else if (!shouldAutoHide) {
@@ -420,16 +415,14 @@
             int alpha = mPaginationPaint.getAlpha();
 
             // Here we draw the dots
-            mPaginationPaint.setAlpha(SHOW_DOT_PAGINATION.get()
-                    ? ((int) (alpha * DOT_ALPHA_FRACTION))
-                    : DOT_ALPHA);
+            mPaginationPaint.setAlpha((int) (alpha * DOT_ALPHA_FRACTION));
             for (int i = 0; i < mNumPages; i++) {
                 canvas.drawCircle(x, y, mDotRadius, mPaginationPaint);
                 x += circleGap;
             }
 
             // Here we draw the current page indicator
-            mPaginationPaint.setAlpha(SHOW_DOT_PAGINATION.get() ? alpha : PAGE_INDICATOR_ALPHA);
+            mPaginationPaint.setAlpha(alpha);
             canvas.drawRoundRect(getActiveRect(), mDotRadius, mDotRadius, mPaginationPaint);
         }
     }
@@ -498,7 +491,7 @@
         @Override
         public void onAnimationEnd(Animator animation) {
             if (!mCancelled) {
-                if (mShouldAutoHide && SHOW_DOT_PAGINATION.get()) {
+                if (mShouldAutoHide) {
                     hideAfterDelay();
                 }
                 mAnimator = null;
diff --git a/src/com/android/launcher3/pm/UserCache.java b/src/com/android/launcher3/pm/UserCache.java
index 92822ab..e2b1286 100644
--- a/src/com/android/launcher3/pm/UserCache.java
+++ b/src/com/android/launcher3/pm/UserCache.java
@@ -17,6 +17,7 @@
 package com.android.launcher3.pm;
 
 import static com.android.launcher3.Utilities.ATLEAST_U;
+import static com.android.launcher3.uioverrides.ApiWrapper.queryAllUsers;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
 import android.content.Context;
@@ -24,7 +25,6 @@
 import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.util.ArrayMap;
 
 import androidx.annotation.AnyThread;
 import androidx.annotation.NonNull;
@@ -33,6 +33,7 @@
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.SimpleBroadcastReceiver;
+import com.android.launcher3.util.UserIconInfo;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -65,7 +66,7 @@
     private final Context mContext;
 
     @NonNull
-    private Map<UserHandle, Long> mUserToSerialMap;
+    private Map<UserHandle, UserIconInfo> mUserToSerialMap;
 
     private UserCache(Context context) {
         mContext = context;
@@ -103,7 +104,7 @@
 
     @WorkerThread
     private void updateCache() {
-        mUserToSerialMap = queryAllUsers(mContext.getSystemService(UserManager.class));
+        mUserToSerialMap = queryAllUsers(mContext);
     }
 
     /**
@@ -118,19 +119,26 @@
      * @see UserManager#getSerialNumberForUser(UserHandle)
      */
     public long getSerialNumberForUser(UserHandle user) {
-        Long serial = mUserToSerialMap.get(user);
-        return serial == null ? 0 : serial;
+        return getUserInfo(user).userSerial;
+    }
+
+    /**
+     * Returns the user properties for the provided user or default values
+     */
+    @NonNull
+    public UserIconInfo getUserInfo(UserHandle user) {
+        UserIconInfo info = mUserToSerialMap.get(user);
+        return info == null ? new UserIconInfo(user, UserIconInfo.TYPE_MAIN) : info;
     }
 
     /**
      * @see UserManager#getUserForSerialNumber(long)
      */
     public UserHandle getUserForSerialNumber(long serialNumber) {
-        Long value = serialNumber;
         return mUserToSerialMap
                 .entrySet()
                 .stream()
-                .filter(entry -> value.equals(entry.getValue()))
+                .filter(entry -> serialNumber == entry.getValue().userSerial)
                 .findFirst()
                 .map(Map.Entry::getKey)
                 .orElse(Process.myUserHandle());
@@ -142,16 +150,4 @@
     public List<UserHandle> getUserProfiles() {
         return List.copyOf(mUserToSerialMap.keySet());
     }
-
-    private static Map<UserHandle, Long> queryAllUsers(UserManager userManager) {
-        Map<UserHandle, Long> users = new ArrayMap<>();
-        List<UserHandle> usersActual = userManager.getUserProfiles();
-        if (usersActual != null) {
-            for (UserHandle user : usersActual) {
-                long serial = userManager.getSerialNumberForUser(user);
-                users.put(user, serial);
-            }
-        }
-        return users;
-    }
 }
diff --git a/src/com/android/launcher3/qsb/QsbContainerView.java b/src/com/android/launcher3/qsb/QsbContainerView.java
index 1e3be27..f0f376f 100644
--- a/src/com/android/launcher3/qsb/QsbContainerView.java
+++ b/src/com/android/launcher3/qsb/QsbContainerView.java
@@ -20,6 +20,8 @@
 import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID;
 import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_PROVIDER;
 
+import static com.android.launcher3.config.FeatureFlags.shouldShowFirstPageWidget;
+
 import android.app.Activity;
 import android.app.Fragment;
 import android.app.SearchManager;
@@ -290,7 +292,7 @@
         }
 
         public boolean isQsbEnabled() {
-            return FeatureFlags.QSB_ON_FIRST_SCREEN;
+            return FeatureFlags.QSB_ON_FIRST_SCREEN && !shouldShowFirstPageWidget();
         }
 
         protected Bundle createBindOptions() {
diff --git a/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
index bcbb3f0..45174a7 100644
--- a/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
+++ b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
@@ -95,9 +95,7 @@
                 EXTRA_ICONS_COUNT
         if (FeatureFlags.ALL_APPS_GONE_VISIBILITY.get()) {
             val grid = ActivityContext.lookupContext<T>(context).deviceProfile
-            val approxRows =
-                Math.ceil((grid.availableHeightPx / grid.allAppsIconSizePx).toDouble()).toInt()
-            targetPreinflateCount += (approxRows + 1) * grid.numShownAllAppsColumns
+            targetPreinflateCount += grid.maxAllAppsRowCount * grid.numShownAllAppsColumns
         }
         if (hasWorkProfile) {
             targetPreinflateCount *= 2
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
index a10c0ad..910b029 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -36,7 +36,6 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
-import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.ActivityAllAppsContainerView;
@@ -56,7 +55,6 @@
 import com.android.launcher3.touch.ItemClickHandler.ItemClickProxy;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.IntSet;
-import com.android.launcher3.util.OnboardingPrefs;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.Themes;
@@ -82,7 +80,6 @@
     private boolean mAppDrawerShown = false;
 
     private StringCache mStringCache;
-    private OnboardingPrefs<?> mOnboardingPrefs;
     private boolean mBindingItems = false;
     private SecondaryDisplayPredictions mSecondaryDisplayPredictions;
 
@@ -93,7 +90,6 @@
         super.onCreate(savedInstanceState);
         mModel = LauncherAppState.getInstance(this).getModel();
         mDragController = new SecondaryDragController(this);
-        mOnboardingPrefs = new OnboardingPrefs<>(this, LauncherPrefs.getPrefs(this));
         mSecondaryDisplayPredictions = SecondaryDisplayPredictions.newInstance(this);
         if (getWindow().getDecorView().isAttachedToWindow()) {
             initUi();
@@ -272,11 +268,6 @@
     }
 
     @Override
-    public OnboardingPrefs<?> getOnboardingPrefs() {
-        return mOnboardingPrefs;
-    }
-
-    @Override
     public void startBinding() {
         mBindingItems = true;
         mDragController.cancelDrag();
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index 7b4e248..6950fb5 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -25,14 +25,13 @@
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.window.WindowManagerProxy.MIN_TABLET_WIDTH;
 
-import android.app.Activity;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
 import android.os.Handler;
 import android.os.Message;
 
-import androidx.annotation.Nullable;
+import androidx.annotation.NonNull;
 import androidx.annotation.WorkerThread;
 
 import com.android.launcher3.BaseActivity;
@@ -62,8 +61,8 @@
     public static final int REQUEST_ROTATE = 1;
     public static final int REQUEST_LOCK = 2;
 
-    @Nullable
-    private BaseActivity mActivity;
+    @NonNull
+    private final BaseActivity mActivity;
     private final Handler mRequestOrientationHandler;
 
     private boolean mIgnoreAutoRotateSettings;
@@ -92,14 +91,14 @@
     // Initialize mLastActivityFlags to a value not used by SCREEN_ORIENTATION flags
     private int mLastActivityFlags = -999;
 
-    public RotationHelper(BaseActivity activity) {
+    public RotationHelper(@NonNull BaseActivity activity) {
         mActivity = activity;
         mRequestOrientationHandler =
                 new Handler(UI_HELPER_EXECUTOR.getLooper(), this::setOrientationAsync);
     }
 
-    private void setIgnoreAutoRotateSettings(boolean ignoreAutoRotateSettings,
-            DisplayController.Info info) {
+    private void setIgnoreAutoRotateSettings(boolean ignoreAutoRotateSettings) {
+        if (mDestroyed) return;
         // On large devices we do not handle auto-rotate differently.
         mIgnoreAutoRotateSettings = ignoreAutoRotateSettings;
         if (!mIgnoreAutoRotateSettings) {
@@ -122,58 +121,54 @@
 
     @Override
     public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) {
+        if (mDestroyed) return;
         boolean ignoreAutoRotateSettings = info.isTablet(info.realBounds);
         if (mIgnoreAutoRotateSettings != ignoreAutoRotateSettings) {
-            setIgnoreAutoRotateSettings(ignoreAutoRotateSettings, info);
+            setIgnoreAutoRotateSettings(ignoreAutoRotateSettings);
             notifyChange();
         }
     }
 
     public void setStateHandlerRequest(int request) {
-        if (mStateHandlerRequest != request) {
-            mStateHandlerRequest = request;
-            notifyChange();
-        }
+        if (mDestroyed || mStateHandlerRequest == request) return;
+        mStateHandlerRequest = request;
+        notifyChange();
     }
 
     public void setCurrentTransitionRequest(int request) {
-        if (mCurrentTransitionRequest != request) {
-            mCurrentTransitionRequest = request;
-            notifyChange();
-        }
+        if (mDestroyed || mCurrentTransitionRequest == request) return;
+        mCurrentTransitionRequest = request;
+        notifyChange();
     }
 
     public void setCurrentStateRequest(int request) {
-        if (mCurrentStateRequest != request) {
-            mCurrentStateRequest = request;
-            notifyChange();
-        }
+        if (mDestroyed || mCurrentStateRequest == request) return;
+        mCurrentStateRequest = request;
+        notifyChange();
     }
 
     // Used by tests only.
     public void forceAllowRotationForTesting(boolean allowRotation) {
+        if (mDestroyed) return;
         mForceAllowRotationForTesting = allowRotation;
         notifyChange();
     }
 
     public void initialize() {
-        if (!mInitialized) {
-            mInitialized = true;
-            DisplayController displayController = DisplayController.INSTANCE.get(mActivity);
-            DisplayController.Info info = displayController.getInfo();
-            setIgnoreAutoRotateSettings(info.isTablet(info.realBounds), info);
-            displayController.addChangeListener(this);
-            notifyChange();
-        }
+        if (mInitialized) return;
+        mInitialized = true;
+        DisplayController displayController = DisplayController.INSTANCE.get(mActivity);
+        DisplayController.Info info = displayController.getInfo();
+        setIgnoreAutoRotateSettings(info.isTablet(info.realBounds));
+        displayController.addChangeListener(this);
+        notifyChange();
     }
 
     public void destroy() {
-        if (!mDestroyed) {
-            mDestroyed = true;
-            DisplayController.INSTANCE.get(mActivity).removeChangeListener(this);
-            LauncherPrefs.get(mActivity).removeListener(this, ALLOW_ROTATION);
-            mActivity = null;
-        }
+        if (mDestroyed) return;
+        mDestroyed = true;
+        DisplayController.INSTANCE.get(mActivity).removeChangeListener(this);
+        LauncherPrefs.get(mActivity).removeListener(this, ALLOW_ROTATION);
     }
 
     private void notifyChange() {
@@ -206,10 +201,8 @@
 
     @WorkerThread
     private boolean setOrientationAsync(Message msg) {
-        Activity activity = mActivity;
-        if (activity != null) {
-            activity.setRequestedOrientation(msg.what);
-        }
+        if (mDestroyed) return true;
+        mActivity.setRequestedOrientation(msg.what);
         return true;
     }
 
@@ -228,8 +221,10 @@
     public String toString() {
         return String.format("[mStateHandlerRequest=%d, mCurrentStateRequest=%d, "
                         + "mLastActivityFlags=%d, mIgnoreAutoRotateSettings=%b, "
-                        + "mHomeRotationEnabled=%b, mForceAllowRotationForTesting=%b]",
+                        + "mHomeRotationEnabled=%b, mForceAllowRotationForTesting=%b,"
+                        + " mDestroyed=%b]",
                 mStateHandlerRequest, mCurrentStateRequest, mLastActivityFlags,
-                mIgnoreAutoRotateSettings, mHomeRotationEnabled, mForceAllowRotationForTesting);
+                mIgnoreAutoRotateSettings, mHomeRotationEnabled, mForceAllowRotationForTesting,
+                mDestroyed);
     }
 }
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index a75f326..0438e57 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -16,7 +16,7 @@
 package com.android.launcher3.testing;
 
 import static com.android.launcher3.allapps.AllAppsStore.DEFER_UPDATES_TEST;
-import static com.android.launcher3.config.FeatureFlags.enableGridOnlyOverview;
+import static com.android.launcher3.Flags.enableGridOnlyOverview;
 import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
@@ -165,6 +165,11 @@
                 response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, mDeviceProfile.isTablet);
                 return response;
 
+            case TestProtocol.REQUEST_NUM_ALL_APPS_COLUMNS:
+                response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
+                        mDeviceProfile.numShownAllAppsColumns);
+                return response;
+
             case TestProtocol.REQUEST_IS_TWO_PANELS:
                 response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD,
                         FOLDABLE_SINGLE_PAGE.get() ? false : mDeviceProfile.isTwoPanels);
diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java
index 122b1e0..a09e5a4 100644
--- a/src/com/android/launcher3/touch/ItemLongClickListener.java
+++ b/src/com/android/launcher3/touch/ItemLongClickListener.java
@@ -30,6 +30,7 @@
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DropTarget;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.folder.Folder;
@@ -136,6 +137,9 @@
         if (launcher.isWorkspaceLocked()) return false;
         // Return early if an item is already being dragged (e.g. when long-pressing two shortcuts)
         if (launcher.getDragController().isDragging()) return false;
+        // Return early if user is in the middle of selecting split-screen apps
+        if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get() &&
+                launcher.isSplitSelectionEnabled()) return false;
 
         return true;
     }
diff --git a/src/com/android/launcher3/touch/WorkspaceTouchListener.java b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
index 96ae4a3..1232069 100644
--- a/src/com/android/launcher3/touch/WorkspaceTouchListener.java
+++ b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
@@ -40,6 +40,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.Workspace;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.testing.TestLogging;
@@ -205,6 +206,10 @@
                         HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
                 mLauncher.getStatsLogManager().logger().log(LAUNCHER_WORKSPACE_LONGPRESS);
                 mLauncher.showDefaultOptions(mTouchDownPoint.x, mTouchDownPoint.y);
+                if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get() &&
+                        mLauncher.isSplitSelectionEnabled()) {
+                    mLauncher.dismissSplitSelection();
+                }
             } else {
                 cancelLongPress();
             }
diff --git a/src/com/android/launcher3/util/Executors.java b/src/com/android/launcher3/util/Executors.java
index 07000ed..c622b71 100644
--- a/src/com/android/launcher3/util/Executors.java
+++ b/src/com/android/launcher3/util/Executors.java
@@ -49,6 +49,12 @@
             POOL_SIZE, POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
 
     /**
+     * An {@link LooperExecutor} to be used with async task where order is important.
+     */
+    public static final LooperExecutor ORDERED_BG_EXECUTOR = new LooperExecutor(
+            createAndStartNewLooper("BackgroundExecutor", THREAD_PRIORITY_BACKGROUND));
+
+    /**
      * Returns the executor for running tasks on the main thread.
      */
     public static final LooperExecutor MAIN_EXECUTOR =
diff --git a/src/com/android/launcher3/util/MainThreadInitializedObject.java b/src/com/android/launcher3/util/MainThreadInitializedObject.java
index 1cb9994..b966d8e 100644
--- a/src/com/android/launcher3/util/MainThreadInitializedObject.java
+++ b/src/com/android/launcher3/util/MainThreadInitializedObject.java
@@ -93,7 +93,7 @@
      * Abstract Context which allows custom implementations for
      * {@link MainThreadInitializedObject} providers
      */
-    public static abstract class SandboxContext extends ContextWrapper {
+    public static class SandboxContext extends ContextWrapper {
 
         private static final String TAG = "SandboxContext";
 
@@ -165,5 +165,14 @@
         protected <T> T createObject(MainThreadInitializedObject<T> object) {
             return object.mProvider.get(this);
         }
+
+        /**
+         * Put a value into mObjectMap, can be used to put mocked MainThreadInitializedObject
+         * instances into SandboxContext.
+         */
+        @VisibleForTesting
+        public <T> void putObject(MainThreadInitializedObject<T> object, T value) {
+            mObjectMap.put(object, value);
+        }
     }
 }
diff --git a/src/com/android/launcher3/util/OnboardingPrefs.java b/src/com/android/launcher3/util/OnboardingPrefs.java
deleted file mode 100644
index f8f4b5f..0000000
--- a/src/com/android/launcher3/util/OnboardingPrefs.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.util;
-
-import android.content.SharedPreferences;
-import android.util.ArrayMap;
-
-import androidx.annotation.StringDef;
-
-import com.android.launcher3.views.ActivityContext;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Collections;
-import java.util.Map;
-
-/**
- * Stores and retrieves onboarding-related data via SharedPreferences.
- *
- * @param <T> Context which owns these preferences.
- */
-public class OnboardingPrefs<T extends ActivityContext> {
-
-    public static final String HOME_BOUNCE_SEEN = "launcher.apps_view_shown";
-    public static final String HOME_BOUNCE_COUNT = "launcher.home_bounce_count";
-    public static final String HOTSEAT_DISCOVERY_TIP_COUNT = "launcher.hotseat_discovery_tip_count";
-    public static final String HOTSEAT_LONGPRESS_TIP_SEEN = "launcher.hotseat_longpress_tip_seen";
-    public static final String ALL_APPS_VISITED_COUNT = "launcher.all_apps_visited_count";
-    public static final String TASKBAR_EDU_TOOLTIP_STEP = "launcher.taskbar_edu_tooltip_step";
-    // When adding a new key, add it here as well, to be able to reset it from Developer Options.
-    public static final Map<String, String[]> ALL_PREF_KEYS = Map.of(
-            "All Apps Bounce", new String[] { HOME_BOUNCE_SEEN, HOME_BOUNCE_COUNT },
-            "Hybrid Hotseat Education", new String[] { HOTSEAT_DISCOVERY_TIP_COUNT,
-                    HOTSEAT_LONGPRESS_TIP_SEEN },
-            "Taskbar Education", new String[] { TASKBAR_EDU_TOOLTIP_STEP },
-            "All Apps Visited Count", new String[] {ALL_APPS_VISITED_COUNT}
-    );
-
-    /**
-     * Events that either have happened or have not (booleans).
-     */
-    @StringDef(value = {
-            HOME_BOUNCE_SEEN,
-            HOTSEAT_LONGPRESS_TIP_SEEN,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface EventBoolKey {}
-
-    /**
-     * Events that occur multiple times, which we count up to a max defined in {@link #MAX_COUNTS}.
-     */
-    @StringDef(value = {
-            HOME_BOUNCE_COUNT,
-            HOTSEAT_DISCOVERY_TIP_COUNT,
-            ALL_APPS_VISITED_COUNT,
-            TASKBAR_EDU_TOOLTIP_STEP,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface EventCountKey {}
-
-    private static final Map<String, Integer> MAX_COUNTS;
-
-    static {
-        Map<String, Integer> maxCounts = new ArrayMap<>(5);
-        maxCounts.put(HOME_BOUNCE_COUNT, 3);
-        maxCounts.put(HOTSEAT_DISCOVERY_TIP_COUNT, 5);
-        maxCounts.put(ALL_APPS_VISITED_COUNT, 20);
-        maxCounts.put(TASKBAR_EDU_TOOLTIP_STEP, 2);
-        MAX_COUNTS = Collections.unmodifiableMap(maxCounts);
-    }
-
-    protected final T mLauncher;
-    protected final SharedPreferences mSharedPrefs;
-
-    public OnboardingPrefs(T launcher, SharedPreferences sharedPrefs) {
-        mLauncher = launcher;
-        mSharedPrefs = sharedPrefs;
-    }
-
-    /** @return The number of times we have seen the given event. */
-    public int getCount(@EventCountKey String key) {
-        return mSharedPrefs.getInt(key, 0);
-    }
-
-    /** @return Whether we have seen this event enough times, as defined by {@link #MAX_COUNTS}. */
-    public boolean hasReachedMaxCount(@EventCountKey String eventKey) {
-        return hasReachedMaxCount(getCount(eventKey), eventKey);
-    }
-
-    private boolean hasReachedMaxCount(int count, @EventCountKey String eventKey) {
-        return count >= MAX_COUNTS.get(eventKey);
-    }
-
-    /** @return Whether we have seen the given event. */
-    public boolean getBoolean(@EventBoolKey String key) {
-        return mSharedPrefs.getBoolean(key, false);
-    }
-
-    /**
-     * Marks on-boarding preference boolean at true
-     */
-    public void markChecked(String flag) {
-        mSharedPrefs.edit().putBoolean(flag, true).apply();
-    }
-
-    /**
-     * Add 1 to the given event count, if we haven't already reached the max count.
-     *
-     * @return Whether we have now reached the max count.
-     */
-    public boolean incrementEventCount(@EventCountKey String eventKey) {
-        int count = getCount(eventKey);
-        if (hasReachedMaxCount(count, eventKey)) {
-            return true;
-        }
-        count++;
-        mSharedPrefs.edit().putInt(eventKey, count).apply();
-        return hasReachedMaxCount(count, eventKey);
-    }
-
-    /**
-     * Sets the event count to the given value.
-     *
-     * @return Whether we have now reached the max count.
-     */
-    public boolean setEventCount(int count, @EventCountKey String eventKey) {
-        mSharedPrefs.edit().putInt(eventKey, count).apply();
-        return hasReachedMaxCount(count, eventKey);
-    }
-}
diff --git a/src/com/android/launcher3/util/OnboardingPrefs.kt b/src/com/android/launcher3/util/OnboardingPrefs.kt
new file mode 100644
index 0000000..8586c43
--- /dev/null
+++ b/src/com/android/launcher3/util/OnboardingPrefs.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util
+
+import android.content.Context
+import com.android.launcher3.LauncherPrefs
+import com.android.launcher3.LauncherPrefs.Companion.backedUpItem
+
+/** Stores and retrieves onboarding-related data via SharedPreferences. */
+object OnboardingPrefs {
+
+    data class CountedItem(
+        val sharedPrefKey: String,
+        val maxCount: Int,
+    ) {
+        private val prefItem = backedUpItem(sharedPrefKey, 0)
+
+        /** @return The number of times we have seen the given event. */
+        fun get(c: Context): Int {
+            return prefItem.get(c)
+        }
+
+        /** @return Whether we have seen this event enough times, as defined by [.MAX_COUNTS]. */
+        fun hasReachedMax(c: Context): Boolean {
+            return get(c) >= maxCount
+        }
+
+        /**
+         * Add 1 to the given event count, if we haven't already reached the max count.
+         *
+         * @return Whether we have now reached the max count.
+         */
+        fun increment(c: Context): Boolean {
+            val count = get(c)
+            if (count >= maxCount) {
+                return true
+            }
+            return set(count + 1, c)
+        }
+
+        /**
+         * Sets the event count to the given value.
+         *
+         * @return Whether we have now reached the max count.
+         */
+        fun set(count: Int, c: Context): Boolean {
+            LauncherPrefs.get(c).put(prefItem, count)
+            return count >= maxCount
+        }
+    }
+
+    @JvmField val TASKBAR_EDU_TOOLTIP_STEP = CountedItem("launcher.taskbar_edu_tooltip_step", 2)
+
+    @JvmField val HOME_BOUNCE_COUNT = CountedItem("launcher.home_bounce_count", 3)
+
+    @JvmField
+    val HOTSEAT_DISCOVERY_TIP_COUNT = CountedItem("launcher.hotseat_discovery_tip_count", 5)
+
+    @JvmField val ALL_APPS_VISITED_COUNT = CountedItem("launcher.all_apps_visited_count", 20)
+
+    @JvmField val HOME_BOUNCE_SEEN = backedUpItem("launcher.apps_view_shown", false)
+
+    @JvmField
+    val HOTSEAT_LONGPRESS_TIP_SEEN = backedUpItem("launcher.hotseat_longpress_tip_seen", false)
+}
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index 04f2ffa..3921e12 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -74,7 +74,6 @@
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.popup.PopupDataProvider;
 import com.android.launcher3.util.ActivityOptionsWrapper;
-import com.android.launcher3.util.OnboardingPrefs;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.RunnableList;
@@ -146,6 +145,15 @@
     }
 
     /**
+     * @return {@code true} if user has selected the first split app and is in the process of
+     *         selecting the second
+     */
+    default boolean isSplitSelectionEnabled() {
+        // Overridden
+        return false;
+    }
+
+    /**
      * The root view to support drag-and-drop and popup support.
      */
     BaseDragLayer getDragLayer();
@@ -222,12 +230,6 @@
      */
     default void applyOverwritesToLogItem(LauncherAtom.ItemInfo.Builder itemInfoBuilder) { }
 
-    /** Onboarding preferences for any onboarding data within this context. */
-    @Nullable
-    default OnboardingPrefs<?> getOnboardingPrefs() {
-        return null;
-    }
-
     /** Returns {@code true} if items are currently being bound within this context. */
     default boolean isBindingItems() {
         return false;
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index dcc86a1..fc9c774 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -16,7 +16,7 @@
 package com.android.launcher3.widget;
 
 import static com.android.app.animation.Interpolators.EMPHASIZED;
-import static com.android.launcher3.config.FeatureFlags.LARGE_SCREEN_WIDGET_PICKER;
+import static com.android.launcher3.LauncherPrefs.WIDGETS_EDUCATION_TIP_SEEN;
 
 import android.content.Context;
 import android.graphics.Canvas;
@@ -41,6 +41,7 @@
 import com.android.launcher3.DropTarget.DragObject;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.dragndrop.DragOptions;
@@ -64,8 +65,6 @@
     /** The default number of cells that can fit horizontally in a widget sheet. */
     public static final int DEFAULT_MAX_HORIZONTAL_SPANS = 4;
 
-    protected static final String KEY_WIDGETS_EDUCATION_TIP_SEEN =
-            "launcher.widgets_education_tip_seen";
     protected final Rect mInsets = new Rect();
 
     /* Touch handling related member variables. */
@@ -194,9 +193,7 @@
         int widthUsed;
         if (deviceProfile.isTablet) {
             int margin = deviceProfile.allAppsLeftRightMargin;
-            if (deviceProfile.isLandscape
-                    && LARGE_SCREEN_WIDGET_PICKER.get()
-                    && !deviceProfile.isTwoPanels) {
+            if (deviceProfile.isLandscape && !deviceProfile.isTwoPanels) {
                 margin = getResources().getDimensionPixelSize(
                         R.dimen.widget_picker_landscape_tablet_left_right_margin);
             }
@@ -333,15 +330,14 @@
                         /* arrowXCoord= */coords[0] + view.getWidth() / 2,
                         /* yCoord= */coords[1]);
         if (arrowTipView != null) {
-            mActivityContext.getSharedPrefs().edit()
-                    .putBoolean(KEY_WIDGETS_EDUCATION_TIP_SEEN, true).apply();
+            LauncherPrefs.get(getContext()).put(WIDGETS_EDUCATION_TIP_SEEN, true);
         }
         return arrowTipView;
     }
 
     /** Returns {@code true} if tip has previously been shown on any of {@link BaseWidgetSheet}. */
     protected boolean hasSeenEducationTip() {
-        return mActivityContext.getSharedPrefs().getBoolean(KEY_WIDGETS_EDUCATION_TIP_SEEN, false)
+        return LauncherPrefs.get(getContext()).get(WIDGETS_EDUCATION_TIP_SEEN)
                 || Utilities.isRunningInTestHarness();
     }
 
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 4105a9a..78116ae 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -18,7 +18,7 @@
 import static android.view.View.MeasureSpec.makeMeasureSpec;
 
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
-import static com.android.launcher3.config.FeatureFlags.LARGE_SCREEN_WIDGET_PICKER;
+import static com.android.launcher3.LauncherPrefs.WIDGETS_EDUCATION_DIALOG_SEEN;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGETSTRAY_SEARCHED;
 import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
 
@@ -40,6 +40,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowInsets;
+import android.view.WindowInsetsController;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 import android.widget.Button;
@@ -56,6 +57,7 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.PendingAnimation;
@@ -98,8 +100,6 @@
     // resolution or landscape on phone. This ratio defines the max percentage of content area that
     // the table can display.
     private static final float RECOMMENDATION_TABLE_HEIGHT_RATIO = 0.75f;
-    private static final String KEY_WIDGETS_EDUCATION_DIALOG_SEEN =
-            "launcher.widgets_education_dialog_seen";
 
     private final UserManagerState mUserManagerState = new UserManagerState();
     private final UserHandle mCurrentUser = Process.myUserHandle();
@@ -679,8 +679,7 @@
 
     /** Shows the {@link WidgetsFullSheet} on the launcher. */
     public static WidgetsFullSheet show(Launcher launcher, boolean animate) {
-        boolean isTwoPane = LARGE_SCREEN_WIDGET_PICKER.get()
-                && launcher.getDeviceProfile().isTablet
+        boolean isTwoPane = launcher.getDeviceProfile().isTablet
                 && launcher.getDeviceProfile().isLandscape
                 && (!launcher.getDeviceProfile().isTwoPanels
                     || FeatureFlags.UNFOLDED_WIDGET_PICKER.get());
@@ -798,8 +797,7 @@
         // Checks the orientation of the screen
         if (mOrientation != newConfig.orientation) {
             mOrientation = newConfig.orientation;
-            if (LARGE_SCREEN_WIDGET_PICKER.get()
-                    && mDeviceProfile.isTablet && !mDeviceProfile.isTwoPanels) {
+            if (mDeviceProfile.isTablet && !mDeviceProfile.isTwoPanels) {
                 handleClose(false);
                 show(Launcher.getLauncher(getContext()), false);
             } else {
@@ -821,7 +819,10 @@
     @Override
     public void onDragStart(boolean start, float startDisplacement) {
         super.onDragStart(start, startDisplacement);
-        getWindowInsetsController().hide(WindowInsets.Type.ime());
+        WindowInsetsController insetsController = getWindowInsetsController();
+        if (insetsController != null) {
+            insetsController.hide(WindowInsets.Type.ime());
+        }
     }
 
     @Nullable private View getViewToShowEducationTip() {
@@ -852,15 +853,13 @@
 
     /** Shows education dialog for widgets. */
     private WidgetsEduView showEducationDialog() {
-        mActivityContext.getSharedPrefs().edit()
-                .putBoolean(KEY_WIDGETS_EDUCATION_DIALOG_SEEN, true).apply();
+        LauncherPrefs.get(getContext()).put(WIDGETS_EDUCATION_DIALOG_SEEN, true);
         return WidgetsEduView.showEducationDialog(mActivityContext);
     }
 
     /** Returns {@code true} if education dialog has previously been shown. */
     protected boolean hasSeenEducationDialog() {
-        return mActivityContext.getSharedPrefs()
-                .getBoolean(KEY_WIDGETS_EDUCATION_DIALOG_SEEN, false)
+        return LauncherPrefs.get(getContext()).get(WIDGETS_EDUCATION_DIALOG_SEEN)
                 || Utilities.isRunningInTestHarness();
     }
 
diff --git a/src_build_config/com/android/launcher3/BuildConfig.java b/src_build_config/com/android/launcher3/BuildConfig.java
index 1f2e0e5..3841969 100644
--- a/src_build_config/com/android/launcher3/BuildConfig.java
+++ b/src_build_config/com/android/launcher3/BuildConfig.java
@@ -27,6 +27,11 @@
     public static final boolean QSB_ON_FIRST_SCREEN = true;
 
     /**
+     * Flag to state if the widget on the top of the first screen should be shown.
+     */
+    public static final boolean WIDGET_ON_FIRST_SCREEN = false;
+
+    /**
      * Flag to control various developer centric features
      */
     public static final boolean IS_DEBUG_DEVICE = false;
diff --git a/src_plugins/com/android/systemui/plugins/LauncherOverlayPlugin.java b/src_plugins/com/android/systemui/plugins/LauncherOverlayPlugin.java
index 9e22355..32f0216 100644
--- a/src_plugins/com/android/systemui/plugins/LauncherOverlayPlugin.java
+++ b/src_plugins/com/android/systemui/plugins/LauncherOverlayPlugin.java
@@ -18,7 +18,6 @@
 import android.app.Activity;
 
 import com.android.systemui.plugins.annotations.ProvidesInterface;
-import com.android.systemui.plugins.shared.LauncherExterns;
 import com.android.systemui.plugins.shared.LauncherOverlayManager;
 
 /**
@@ -29,6 +28,6 @@
     String ACTION = "com.android.systemui.action.PLUGIN_LAUNCHER_OVERLAY";
     int VERSION = 1;
 
-    LauncherOverlayManager createOverlayManager(Activity activity, LauncherExterns externs);
+    LauncherOverlayManager createOverlayManager(Activity activity);
 
 }
diff --git a/src_plugins/com/android/systemui/plugins/shared/LauncherExterns.java b/src_plugins/com/android/systemui/plugins/shared/LauncherExterns.java
deleted file mode 100644
index 173b454..0000000
--- a/src_plugins/com/android/systemui/plugins/shared/LauncherExterns.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.plugins.shared;
-
-import android.content.SharedPreferences;
-
-import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlay;
-
-/**
- * This interface defines the set of methods that the Launcher activity exposes. Methods
- * here should be safe to call from classes outside of com.android.launcher3.*
- */
-public interface LauncherExterns {
-
-    /**
-     * Returns the shared main preference
-     */
-    SharedPreferences getSharedPrefs();
-
-    /**
-     * Returns the device specific preference
-     */
-    SharedPreferences getDevicePrefs();
-
-    /**
-     * Sets the overlay on the target activity
-     */
-    void setLauncherOverlay(LauncherOverlay overlay);
-}
diff --git a/src_plugins/com/android/systemui/plugins/shared/LauncherOverlayManager.java b/src_plugins/com/android/systemui/plugins/shared/LauncherOverlayManager.java
index 582ab23..6b27503 100644
--- a/src_plugins/com/android/systemui/plugins/shared/LauncherOverlayManager.java
+++ b/src_plugins/com/android/systemui/plugins/shared/LauncherOverlayManager.java
@@ -41,10 +41,6 @@
 
     default void hideOverlay(int duration) { }
 
-    default boolean startSearch(byte[] config, Bundle extras) {
-        return false;
-    }
-
     @Override
     default void onActivityCreated(Activity activity, Bundle bundle) { }
 
diff --git a/src_shortcuts_overrides/com/android/launcher3/model/LauncherBinder.java b/src_shortcuts_overrides/com/android/launcher3/model/LauncherBinder.java
index e1a5f24..7e73ab5 100644
--- a/src_shortcuts_overrides/com/android/launcher3/model/LauncherBinder.java
+++ b/src_shortcuts_overrides/com/android/launcher3/model/LauncherBinder.java
@@ -51,4 +51,9 @@
                 mBgDataModel.widgetsModel.getWidgetsListForPicker(mApp.getContext());
         executeCallbacksTask(c -> c.bindAllWidgets(widgets), mUiExecutor);
     }
+
+    @Override
+    public void bindSmartspaceWidget() {
+        executeCallbacksTask(c -> c.bindSmartspaceWidget(), mUiExecutor);
+    }
 }
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
index 599a591..fe5c1fd 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -21,10 +21,16 @@
 import android.content.Context;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.ShortcutInfo;
+import android.graphics.drawable.ColorDrawable;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.ArrayMap;
 
 import com.android.launcher3.Utilities;
+import com.android.launcher3.util.UserIconInfo;
 
 import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -48,4 +54,41 @@
     public static ActivityOptions createFadeOutAnimOptions(Context context) {
         return ActivityOptions.makeCustomAnimation(context, 0, android.R.anim.fade_out);
     }
+
+    /**
+     * Returns a map of all users on the device to their corresponding UI properties
+     */
+    public static Map<UserHandle, UserIconInfo> queryAllUsers(Context context) {
+        UserManager um = context.getSystemService(UserManager.class);
+        Map<UserHandle, UserIconInfo> users = new ArrayMap<>();
+        List<UserHandle> usersActual = um.getUserProfiles();
+        if (usersActual != null) {
+            for (UserHandle user : usersActual) {
+                long serial = um.getSerialNumberForUser(user);
+
+                // Simple check to check if the provided user is work profile
+                // TODO: Migrate to a better platform API
+                NoopDrawable d = new NoopDrawable();
+                boolean isWork = (d != context.getPackageManager().getUserBadgedIcon(d, user));
+                UserIconInfo info = new UserIconInfo(
+                        user,
+                        isWork ? UserIconInfo.TYPE_WORK : UserIconInfo.TYPE_MAIN,
+                        serial);
+                users.put(user, info);
+            }
+        }
+        return users;
+    }
+
+    private static class NoopDrawable extends ColorDrawable {
+        @Override
+        public int getIntrinsicHeight() {
+            return 1;
+        }
+
+        @Override
+        public int getIntrinsicWidth() {
+            return 1;
+        }
+    }
 }
diff --git a/tests/Android.bp b/tests/Android.bp
index 62d232c..4988dbd 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -42,7 +42,8 @@
 filegroup {
     name: "launcher-oop-tests-src",
     srcs: [
-      "src/com/android/launcher3/allapps/OopTaplOpenCloseAllApps.java",
+      "src/com/android/launcher3/allapps/TaplOpenCloseAllApps.java",
+      "src/com/android/launcher3/allapps/TaplTestsAllAppsIconsWorking.java",
       "src/com/android/launcher3/appiconmenu/TaplAppIconMenuTest.java",
       "src/com/android/launcher3/dragging/TaplDragTest.java",
       "src/com/android/launcher3/dragging/TaplUninstallRemove.java",
@@ -50,7 +51,9 @@
       "src/com/android/launcher3/ui/PortraitLandscapeRunner.java",
       "src/com/android/launcher3/ui/TaplTestsLauncher3.java",
       "src/com/android/launcher3/ui/widget/TaplWidgetPickerTest.java",
+      "src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java",
       "src/com/android/launcher3/util/LauncherLayoutBuilder.java",
+      "src/com/android/launcher3/util/TestConstants.java",
       "src/com/android/launcher3/util/TestUtil.java",
       "src/com/android/launcher3/util/Wait.java",
       "src/com/android/launcher3/util/WidgetUtils.java",
@@ -61,7 +64,6 @@
       "src/com/android/launcher3/util/rule/ShellCommandRule.java",
       "src/com/android/launcher3/util/rule/TestIsolationRule.java",
       "src/com/android/launcher3/util/rule/TestStabilityRule.java",
-      "src/com/android/launcher3/util/rule/TISBindRule.java",
       "src/com/android/launcher3/util/viewcapture_analysis/*.java",
       "src/com/android/launcher3/testcomponent/BaseTestingActivity.java",
       "src/com/android/launcher3/testcomponent/OtherBaseTestingActivity.java",
@@ -89,12 +91,14 @@
         "androidx.test.espresso.contrib",
         "androidx.test.espresso.intents",
         "androidx.test.uiautomator_uiautomator",
+        "mockito-kotlin2",
         "mockito-target-extended-minus-junit4",
         "launcher_log_protos_lite",
-        "truth-prebuilt",
+        "truth",
         "platform-test-rules",
         "testables",
         "com_android_launcher3_flags_lib",
+        "com_android_wm_shell_flags_lib",
     ],
     manifest: "AndroidManifest-common.xml",
     platform_apis: true,
diff --git a/tests/OWNERS b/tests/OWNERS
index 6b8643c..b5ee7d7 100644
--- a/tests/OWNERS
+++ b/tests/OWNERS
@@ -3,3 +3,4 @@
 sunnygoyal@google.com
 winsonc@google.com
 hyunyoungs@google.com
+mateuszc@google.com
diff --git a/tests/assets/databases/workspace_items.sql b/tests/assets/databases/workspace_items.sql
new file mode 100644
index 0000000..68f7d50
--- /dev/null
+++ b/tests/assets/databases/workspace_items.sql
@@ -0,0 +1,79 @@
+DROP TABLE IF EXISTS 'favorites';
+CREATE TABLE favorites (_id INTEGER PRIMARY KEY,title TEXT,intent TEXT,container INTEGER,screen INTEGER,cellX INTEGER,cellY INTEGER,spanX INTEGER,spanY INTEGER,itemType INTEGER,appWidgetId INTEGER NOT NULL DEFAULT -1,icon BLOB,appWidgetProvider TEXT,modified INTEGER NOT NULL DEFAULT 0,restored INTEGER NOT NULL DEFAULT 0,profileId INTEGER DEFAULT 0,rank INTEGER NOT NULL DEFAULT 0,options INTEGER NOT NULL DEFAULT 0,appWidgetSource INTEGER NOT NULL DEFAULT -1);
+INSERT INTO 'favorites' VALUES(1,'Phone','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.dialer/.extensions.GoogleDialtactsActivity;end',-101,0,0,0,1,1,0,-1,NULL,NULL,0,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(2,'Messages','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.messaging/.ui.ConversationListActivity;end',-101,1,1,0,1,1,0,-1,NULL,NULL,0,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(3,'Play Store','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.android.vending/.AssetBrowserActivity;end',-101,2,2,0,1,1,0,-1,NULL,NULL,0,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(4,'Chrome','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.android.chrome/com.google.android.apps.chrome.Main;end',-101,3,3,0,1,1,0,-1,NULL,NULL,0,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(6,'Settings','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.android.settings/.Settings;end',-100,0,0,1,1,1,0,-1,X'',NULL,1693590010654,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(7,'Drive','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.docs/.app.NewMainProxyActivity;end',-100,0,1,2,1,1,0,-1,X'',NULL,1693589533751,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(8,'Better Bug','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.internal.betterbug/com.google.android.apps.betterbug.bugslist.BugsListActivity;end',16,0,0,0,1,1,0,-1,X'',NULL,1693589597917,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(9,'Flag Flipper','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.android.theflippinapp/.MainActivity;end',-100,0,3,2,1,1,0,-1,X'',NULL,1693589546863,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(10,NULL,NULL,-100,0,1,3,3,2,4,3,NULL,'com.google.android.apps.docs/com.google.android.apps.docs.drive.widget.suggestion.SuggestionAppWidgetProvider',1693589554018,0,0,0,0,-112);
+INSERT INTO 'favorites' VALUES(11,'Scan','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.google.android.apps.docs;component=com.google.android.apps.docs/.app.NewMainProxyActivity;S.shortcut_id=launcher_shortcut_scan;end',-100,0,0,3,1,1,6,-1,X'',NULL,1693589559601,0,0,1,0,-1);
+INSERT INTO 'favorites' VALUES(12,'Upload','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.google.android.apps.docs;component=com.google.android.apps.docs/.app.NewMainProxyActivity;S.shortcut_id=launcher_shortcut_upload;end',-100,0,0,4,1,1,6,-1,X'',NULL,1693589576040,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(13,'Search','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.google.android.apps.docs;component=com.google.android.apps.docs/.app.NewMainProxyActivity;S.shortcut_id=launcher_shortcut_search;end',-100,0,4,4,1,1,6,-1,X'',NULL,1693589582487,0,0,2,0,-1);
+INSERT INTO 'favorites' VALUES(15,'BluetoothCompanionApp','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.android.bluetooth.companion/.MainActivity;end',-100,0,1,1,1,1,0,-1,X'',NULL,1693589594115,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(16,'Better Bug',NULL,-100,0,3,1,1,1,2,-1,NULL,NULL,1693589600675,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(17,'Better Bug','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.internal.betterbug/com.google.android.apps.betterbug.bugslist.BugsListActivity;end',16,0,1,0,1,1,0,-1,X'',NULL,1693589597936,0,0,1,0,-1);
+INSERT INTO 'favorites' VALUES(18,'Better Bug','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.internal.betterbug/com.google.android.apps.betterbug.bugslist.BugsListActivity;end',16,0,0,1,1,1,0,-1,X'',NULL,1693589603123,0,0,2,0,-1);
+INSERT INTO 'favorites' VALUES(19,'Incognito Tab','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.android.chrome;component=com.android.chrome/com.google.android.apps.chrome.Main;S.shortcut_id=dynamic-new-incognito-tab-shortcut;end',-100,0,2,2,1,1,6,-1,X'',NULL,1693589609963,0,0,1,0,-1);
+INSERT INTO 'favorites' VALUES(20,NULL,NULL,-100,1,0,0,3,2,4,5,NULL,'com.google.android.deskclock/com.android.alarmclock.AnalogAppWidgetProvider',1693589630235,0,0,0,0,-112);
+INSERT INTO 'favorites' VALUES(21,NULL,NULL,-100,1,1,3,2,2,4,6,NULL,'com.android.chrome/org.chromium.chrome.browser.quickactionsearchwidget.QuickActionSearchWidgetProvider$QuickActionSearchWidgetProviderDino',1693589677239,0,0,0,0,-112);
+INSERT INTO 'favorites' VALUES(22,'New tab','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.android.chrome;component=com.android.chrome/com.google.android.apps.chrome.Main;S.shortcut_id=new-tab-shortcut;end',36,0,0,0,1,1,6,-1,X'',NULL,1693589694253,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(25,'Selfie','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.google.android.GoogleCamera;component=com.google.android.GoogleCamera/com.android.camera.CameraLauncher;S.shortcut_id=selfie;end',33,0,0,0,1,1,6,-1,X'',NULL,1693589686832,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(26,'Calculator','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.calculator/com.android.calculator2.Calculator;end',-100,1,4,2,1,1,0,-1,X'',NULL,1693589656954,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(27,'Calendar','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.calendar/com.android.calendar.AllInOneActivity;end',-100,1,4,0,1,1,0,-1,X'',NULL,1693589660516,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(28,'New event','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.google.android.calendar;component=com.google.android.calendar/com.android.calendar.AllInOneActivity;S.shortcut_id=launcher_shortcuts_shortcut_new_event;end',38,0,0,0,1,1,6,-1,X'',NULL,1693589698615,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(29,'New task','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.google.android.calendar;component=com.google.android.calendar/com.android.calendar.AllInOneActivity;S.shortcut_id=launcher_shortcuts_shortcut_create_task;end',-100,1,0,4,1,1,6,-1,X'',NULL,1693589677243,0,0,1,0,-1);
+INSERT INTO 'favorites' VALUES(30,'Contacts','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.contacts/com.android.contacts.activities.PeopleActivity;end',-100,1,4,1,1,1,0,-1,X'',NULL,1693589671550,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(31,'Alex Eaves','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.google.android.contacts;component=com.google.android.contacts/com.android.contacts.activities.PeopleActivity;S.shortcut_id=822i60c772551678bd93;end',-100,1,4,3,1,1,6,-1,X'',NULL,1693589681873,0,0,1,0,-1);
+INSERT INTO 'favorites' VALUES(32,'Add contact','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.google.android.contacts;component=com.google.android.contacts/com.android.contacts.activities.PeopleActivity;S.shortcut_id=shortcut-add-contact;end',-100,1,3,3,1,1,6,-1,X'',NULL,1693589681880,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(33,'Call',NULL,-100,1,3,2,1,1,2,-1,NULL,NULL,1693589687263,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(34,'Contacts','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.contacts/com.android.contacts.activities.PeopleActivity;end',33,0,1,0,1,1,0,-1,X'',NULL,1693589686853,0,0,1,0,-1);
+INSERT INTO 'favorites' VALUES(35,'Gmail','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.gm/.ConversationListActivityGmail;end',33,0,0,1,1,1,0,-1,X'',NULL,1693589690561,0,0,2,0,-1);
+INSERT INTO 'favorites' VALUES(36,'Files',NULL,-100,1,3,1,1,1,2,-1,NULL,NULL,1693589706696,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(37,'Files','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.nbu.files/.home.HomeActivity;end',36,0,1,0,1,1,0,-1,X'',NULL,1693589694261,0,0,1,0,-1);
+INSERT INTO 'favorites' VALUES(38,NULL,NULL,-100,1,0,3,1,1,2,-1,NULL,NULL,1693589698611,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(39,'Scan','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.google.android.apps.docs;component=com.google.android.apps.docs/.app.NewMainProxyActivity;S.shortcut_id=launcher_shortcut_scan;end',38,0,1,0,1,1,6,-1,X'',NULL,1693589698621,0,0,1,0,-1);
+INSERT INTO 'favorites' VALUES(40,'Search','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.google.android.apps.docs;component=com.google.android.apps.docs/.app.NewMainProxyActivity;S.shortcut_id=launcher_shortcut_search;end',-100,1,3,4,1,1,6,-1,X'',NULL,1693589702696,0,0,2,0,-1);
+INSERT INTO 'favorites' VALUES(41,'Upload','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.google.android.apps.docs;component=com.google.android.apps.docs/.app.NewMainProxyActivity;S.shortcut_id=launcher_shortcut_upload;end',-100,1,1,2,1,1,6,-1,X'',NULL,1693589706711,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(42,'Camera Obfuscator','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.internal.camera.imageobfuscator/.activities.MainActivity;end',-100,1,3,0,1,1,0,-1,X'',NULL,1693589710458,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(43,'Files','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.nbu.files/.home.HomeActivity;end',45,0,0,0,1,1,0,-1,X'',NULL,1693589727388,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(44,'Flag Flipper','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.android.theflippinapp/.MainActivity;end',-100,2,1,0,1,1,0,-1,X'',NULL,1693589724756,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(45,NULL,NULL,-100,2,0,0,1,1,2,-1,NULL,NULL,1693589727385,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(46,'Flag Flipper','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.android.theflippinapp/.MainActivity;end',45,0,1,0,1,1,0,-1,X'',NULL,1693589727398,0,0,1,0,-1);
+INSERT INTO 'favorites' VALUES(47,'Gmail','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.gm/.ConversationListActivityGmail;end',-100,2,2,0,1,1,0,-1,X'',NULL,1693589730037,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(48,'Compose','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.google.android.gm;component=com.google.android.gm/.ConversationListActivityGmail;S.shortcut_id=manifest_compose_shortcut;end',-100,2,3,0,1,1,6,-1,X'',NULL,1693589733121,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(49,NULL,NULL,-100,2,0,1,3,2,4,7,NULL,'com.google.android.gm/com.google.android.gm.widget.GmailWidgetProvider',1693589740752,0,0,0,0,-112);
+INSERT INTO 'favorites' VALUES(50,NULL,NULL,-100,3,1,0,4,5,4,8,NULL,'com.google.android.calendar/com.google.android.calendar.widgetmonth.MonthViewWidgetProvider',1693589746495,0,0,0,0,-111);
+INSERT INTO 'favorites' VALUES(54,'Maps','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.maps/com.google.android.maps.MapsActivity;end',-100,2,3,2,1,1,0,-1,X'',NULL,1693589785990,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(55,'Productivity',NULL,-100,2,3,4,1,1,2,-1,NULL,NULL,1693589797590,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(56,'Work','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.google.android.apps.maps;component=com.google.android.apps.maps/com.google.android.maps.MapsActivity;S.shortcut_id=manifest_work;end',55,0,1,0,1,1,6,-1,X'',NULL,1693589789538,0,0,1,0,-1);
+INSERT INTO 'favorites' VALUES(57,'Home','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.google.android.apps.maps;component=com.google.android.apps.maps/com.google.android.maps.MapsActivity;S.shortcut_id=manifest_home;end',-100,2,3,1,1,1,6,-1,X'',NULL,1693589793825,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(58,'Gyotaku','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.internal.gyotaku/.Launcher;end',-100,2,3,3,1,1,0,-1,X'',NULL,1693589797615,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(60,'Phone','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.dialer/.extensions.GoogleDialtactsActivity;end',-100,2,4,3,1,1,0,-1,X'',NULL,1693589805582,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(61,'Photos','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.photos/.home.HomeActivity;end',-100,2,4,4,1,1,0,-1,X'',NULL,1693589809050,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(63,'Pixel Logger',NULL,-100,3,0,3,1,1,2,-1,NULL,NULL,1693589820775,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(65,'Pixel Tips','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.tips/.TipsMain;end',-100,3,0,4,1,1,0,-1,X'',NULL,1693589823832,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(66,'Play Store','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.android.vending/.AssetBrowserActivity;end',-100,3,0,1,1,1,0,-1,X'',NULL,1693589834647,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(67,NULL,NULL,-100,4,2,0,3,2,4,10,NULL,'com.google.android.apps.youtube.music/com.google.android.apps.youtube.music.player.widget.gm3.FreeformMusicWidgetProvider',1693589842256,0,0,0,0,-112);
+INSERT INTO 'favorites' VALUES(68,NULL,NULL,-100,4,0,2,4,3,4,11,NULL,'com.google.android.apps.youtube.music/com.google.android.apps.youtube.music.player.widget.gm3.FreeformMusicWidgetProvider',1693589854706,0,0,0,0,-112);
+INSERT INTO 'favorites' VALUES(69,'YouTube','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.youtube/.app.honeycomb.Shell%24HomeActivity;end',-100,4,4,4,1,1,0,-1,X'',NULL,1693589859008,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(70,'Explore','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.google.android.youtube;component=com.google.android.youtube/.app.honeycomb.Shell%24HomeActivity;S.shortcut_id=explore-shortcut;end',-100,4,4,3,1,1,6,-1,X'',NULL,1693589867283,0,0,3,0,-1);
+INSERT INTO 'favorites' VALUES(71,'Search','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.google.android.youtube;component=com.google.android.youtube/.app.honeycomb.Shell%24HomeActivity;S.shortcut_id=search-shortcut;end',-100,4,4,2,1,1,6,-1,X'',NULL,1693589871989,0,0,1,0,-1);
+INSERT INTO 'favorites' VALUES(72,'Shorts','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.google.android.youtube;component=com.google.android.youtube/.app.honeycomb.Shell%24HomeActivity;S.shortcut_id=shorts-shortcut;end',-100,4,0,1,1,1,6,-1,X'',NULL,1693589882256,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(73,'Subscriptions','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.google.android.youtube;component=com.google.android.youtube/.app.honeycomb.Shell%24HomeActivity;S.shortcut_id=subscriptions-shortcut;end',-100,4,0,0,1,1,6,-1,X'',NULL,1693589888244,0,0,2,0,-1);
+INSERT INTO 'favorites' VALUES(74,'Safety','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.safetyhub/.LauncherActivity;end',-100,4,1,1,1,1,0,-1,X'',NULL,1693589891720,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(75,'Wi‑Fi','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.android.settings;component=com.android.settings/.Settings;S.shortcut_id=manifest-shortcut-wifi;end',-100,5,2,0,1,1,6,-1,X'',NULL,1693589897994,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(76,'Data usage','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.android.settings;component=com.android.settings/.Settings;S.shortcut_id=manifest-shortcut-data-usage;end',-100,5,3,1,1,1,6,-1,X'',NULL,1693589904331,0,0,1,0,-1);
+INSERT INTO 'favorites' VALUES(77,'Battery','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.android.settings;component=com.android.settings/.Settings;S.shortcut_id=manifest-shortcut-battery;end',-100,5,1,2,1,1,6,-1,X'',NULL,1693589907795,0,0,2,0,-1);
+INSERT INTO 'favorites' VALUES(78,'Internet','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.android.settings;component=com.android.settings/.Settings;S.shortcut_id=component-shortcut-com.android.settings%2F.Settings%24NetworkProviderSettingsActivity;end',-100,5,2,1,1,1,6,-1,X'',NULL,1693589914187,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(79,'Safety','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.safetyhub/.LauncherActivity;end',-100,5,2,3,1,1,0,-1,X'',NULL,1693589917447,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(80,'Recorder','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.recorder/.ui.recordings.MainActivity;end',-100,5,0,4,1,1,0,-1,X'',NULL,1693589920866,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(81,'Maps','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.maps/com.google.android.maps.MapsActivity;end',82,0,0,0,1,1,0,-1,X'',NULL,1693589929103,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(82,NULL,NULL,-100,5,3,3,1,1,2,-1,NULL,NULL,1693589929099,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(83,'Flag Flipper','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.android.theflippinapp/.MainActivity;end',82,0,1,0,1,1,0,-1,X'',NULL,1693589929134,0,0,1,0,-1);
+INSERT INTO 'favorites' VALUES(84,'Gmail','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.gm/.ConversationListActivityGmail;end',82,0,2,0,1,1,0,-1,X'',NULL,1693589938320,0,0,2,0,-1);
+INSERT INTO 'favorites' VALUES(85,'Google','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.googlequicksearchbox/.SearchActivity;end',82,0,0,1,1,1,0,-1,X'',NULL,1693589938321,0,0,3,0,-1);
+INSERT INTO 'favorites' VALUES(86,'Calendar','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.calendar/com.android.calendar.AllInOneActivity;end',82,0,1,1,1,1,0,-1,X'',NULL,1693589938316,0,0,4,0,-1);
+INSERT INTO 'favorites' VALUES(87,'Chrome','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.android.chrome/com.google.android.apps.chrome.Main;end',82,0,2,1,1,1,0,-1,X'',NULL,1693589941181,0,0,5,0,-1);
diff --git a/tests/shared/com/android/launcher3/testing/OWNERS b/tests/shared/com/android/launcher3/testing/OWNERS
new file mode 100644
index 0000000..a818d5e
--- /dev/null
+++ b/tests/shared/com/android/launcher3/testing/OWNERS
@@ -0,0 +1,5 @@
+vadimt@google.com
+sunnygoyal@google.com
+winsonc@google.com
+hyunyoungs@google.com
+mateuszc@google.com
\ No newline at end of file
diff --git a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
index 54a1c08..51f457d 100644
--- a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
+++ b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
@@ -115,6 +115,7 @@
     public static final String REQUEST_CLEAR_DATA = "clear-data";
     public static final String REQUEST_HOTSEAT_ICON_NAMES = "get-hotseat-icon-names";
     public static final String REQUEST_IS_TABLET = "is-tablet";
+    public static final String REQUEST_NUM_ALL_APPS_COLUMNS = "num-all-apps-columns";
     public static final String REQUEST_IS_TWO_PANELS = "is-two-panel";
     public static final String REQUEST_START_DRAG_THRESHOLD = "start-drag-threshold";
     public static final String REQUEST_SHELL_DRAG_READY = "shell-drag-ready";
diff --git a/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt b/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
index a52ba9e..0b31469 100644
--- a/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
+++ b/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
@@ -24,6 +24,7 @@
 import androidx.test.core.app.ApplicationProvider
 import com.android.launcher3.testing.shared.ResourceUtils
 import com.android.launcher3.util.DisplayController
+import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext
 import com.android.launcher3.util.NavigationMode
 import com.android.launcher3.util.WindowBounds
 import com.android.launcher3.util.rule.TestStabilityRule
@@ -35,13 +36,11 @@
 import java.io.StringWriter
 import kotlin.math.max
 import kotlin.math.min
-import org.junit.After
-import org.junit.Before
 import org.junit.Rule
-import org.mockito.ArgumentMatchers
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.spy
-import org.mockito.Mockito.`when` as whenever
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.whenever
 
 /**
  * This is an abstract class for DeviceProfile tests that create an InvariantDeviceProfile based on
@@ -50,30 +49,14 @@
  * For an implementation that mocks InvariantDeviceProfile, use [FakeInvariantDeviceProfileTest]
  */
 abstract class AbstractDeviceProfileTest {
-    protected var context: Context? = null
+    protected lateinit var context: SandboxContext
     protected open val runningContext: Context = ApplicationProvider.getApplicationContext()
-    private var displayController: DisplayController = mock(DisplayController::class.java)
-    private var windowManagerProxy: WindowManagerProxy = mock(WindowManagerProxy::class.java)
-    private lateinit var originalDisplayController: DisplayController
-    private lateinit var originalWindowManagerProxy: WindowManagerProxy
+    private val displayController: DisplayController = mock()
+    private val windowManagerProxy: WindowManagerProxy = mock()
+    private val launcherPrefs: LauncherPrefs = mock()
 
     @Rule @JvmField val testStabilityRule = TestStabilityRule()
 
-    @Before
-    open fun setUp() {
-        val appContext: Context = ApplicationProvider.getApplicationContext()
-        originalWindowManagerProxy = WindowManagerProxy.INSTANCE.get(appContext)
-        originalDisplayController = DisplayController.INSTANCE.get(appContext)
-        WindowManagerProxy.INSTANCE.initializeForTesting(windowManagerProxy)
-        DisplayController.INSTANCE.initializeForTesting(displayController)
-    }
-
-    @After
-    open fun tearDown() {
-        WindowManagerProxy.INSTANCE.initializeForTesting(originalWindowManagerProxy)
-        DisplayController.INSTANCE.initializeForTesting(originalDisplayController)
-    }
-
     class DeviceSpec(
         val naturalSize: Pair<Int, Int>,
         var densityDpi: Int,
@@ -288,11 +271,10 @@
     ) {
         val windowsBounds = perDisplayBoundsCache[displayInfo]!!
         val realBounds = windowsBounds[rotation]
-        whenever(windowManagerProxy.getDisplayInfo(ArgumentMatchers.any())).thenReturn(displayInfo)
-        whenever(windowManagerProxy.getRealBounds(ArgumentMatchers.any(), ArgumentMatchers.any()))
-            .thenReturn(realBounds)
-        whenever(windowManagerProxy.getRotation(ArgumentMatchers.any())).thenReturn(rotation)
-        whenever(windowManagerProxy.getNavigationMode(ArgumentMatchers.any()))
+        whenever(windowManagerProxy.getDisplayInfo(any())).thenReturn(displayInfo)
+        whenever(windowManagerProxy.getRealBounds(any(), any())).thenReturn(realBounds)
+        whenever(windowManagerProxy.getRotation(any())).thenReturn(rotation)
+        whenever(windowManagerProxy.getNavigationMode(any()))
             .thenReturn(
                 if (isGestureMode) NavigationMode.NO_BUTTON else NavigationMode.THREE_BUTTONS
             )
@@ -305,8 +287,19 @@
                 screenHeightDp = (realBounds.bounds.height() / density).toInt()
                 smallestScreenWidthDp = min(screenWidthDp, screenHeightDp)
             }
-        context = runningContext.createConfigurationContext(config)
+        val configurationContext = runningContext.createConfigurationContext(config)
+        context =
+            SandboxContext(
+                configurationContext,
+                DisplayController.INSTANCE,
+                WindowManagerProxy.INSTANCE,
+                LauncherPrefs.INSTANCE
+            )
+        context.putObject(DisplayController.INSTANCE, displayController)
+        context.putObject(WindowManagerProxy.INSTANCE, windowManagerProxy)
+        context.putObject(LauncherPrefs.INSTANCE, launcherPrefs)
 
+        whenever(launcherPrefs.get(LauncherPrefs.TASKBAR_PINNING)).thenReturn(false)
         val info = spy(DisplayController.Info(context, windowManagerProxy, perDisplayBoundsCache))
         whenever(displayController.info).thenReturn(info)
         whenever(info.isTransientTaskbar).thenReturn(isGestureMode)
diff --git a/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt b/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
index 42338bf..a421006 100644
--- a/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
+++ b/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
@@ -27,9 +27,9 @@
 import java.io.PrintWriter
 import java.io.StringWriter
 import org.junit.Before
-import org.mockito.ArgumentMatchers.any
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.`when` as whenever
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
 
 /**
  * This is an abstract class for DeviceProfile tests that don't need the real Context and mock an
@@ -41,7 +41,7 @@
 
     protected var context: Context? = null
     protected var inv: InvariantDeviceProfile? = null
-    protected var info: Info = mock(Info::class.java)
+    protected val info: Info = mock()
     protected var windowBounds: WindowBounds? = null
     protected var isMultiWindowMode: Boolean = false
     protected var transposeLayoutWithOrientation: Boolean = false
diff --git a/tests/src/com/android/launcher3/LauncherPrefsTest.kt b/tests/src/com/android/launcher3/LauncherPrefsTest.kt
index d59e02a..88a430b 100644
--- a/tests/src/com/android/launcher3/LauncherPrefsTest.kt
+++ b/tests/src/com/android/launcher3/LauncherPrefsTest.kt
@@ -33,7 +33,8 @@
 private val TEST_BOOLEAN_ITEM = LauncherPrefs.nonRestorableItem("1", false)
 private val TEST_STRING_ITEM = LauncherPrefs.nonRestorableItem("2", "( ͡❛ ͜ʖ ͡❛)")
 private val TEST_INT_ITEM = LauncherPrefs.nonRestorableItem("3", -1)
-private val TEST_CONTEXTUAL_ITEM = ContextualItem("4", true, { true }, false, Boolean::class.java)
+private val TEST_CONTEXTUAL_ITEM =
+    ContextualItem("4", true, { true }, EncryptionType.ENCRYPTED, Boolean::class.java)
 
 private const val TEST_DEFAULT_VALUE = "default"
 private const val TEST_PREF_KEY = "test_pref_key"
@@ -51,13 +52,13 @@
         @BeforeClass
         @JvmStatic
         fun setup() {
-            isBootAwareStartupDataEnabled = true
+            moveStartupDataToDeviceProtectedStorageIsEnabled = true
         }
 
         @AfterClass
         @JvmStatic
         fun teardown() {
-            isBootAwareStartupDataEnabled = false
+            moveStartupDataToDeviceProtectedStorageIsEnabled = false
         }
     }
 
@@ -203,7 +204,11 @@
     @Test
     fun put_bootAwareItem_updatesDeviceProtectedStorage() {
         val bootAwareItem =
-            LauncherPrefs.backedUpItem(TEST_PREF_KEY, TEST_DEFAULT_VALUE, isBootAware = true)
+            LauncherPrefs.backedUpItem(
+                TEST_PREF_KEY,
+                TEST_DEFAULT_VALUE,
+                EncryptionType.DEVICE_PROTECTED
+            )
 
         val bootAwarePrefs: SharedPreferences =
             context
@@ -220,7 +225,11 @@
     @Test
     fun put_bootAwareItem_updatesEncryptedStorage() {
         val bootAwareItem =
-            LauncherPrefs.backedUpItem(TEST_PREF_KEY, TEST_DEFAULT_VALUE, isBootAware = true)
+            LauncherPrefs.backedUpItem(
+                TEST_PREF_KEY,
+                TEST_DEFAULT_VALUE,
+                EncryptionType.MOVE_TO_DEVICE_PROTECTED
+            )
 
         val encryptedPrefs: SharedPreferences =
             context.getSharedPreferences(bootAwareItem.sharedPrefFile, Context.MODE_PRIVATE)
@@ -235,7 +244,11 @@
     @Test
     fun remove_bootAwareItem_removesFromDeviceProtectedStorage() {
         val bootAwareItem =
-            LauncherPrefs.backedUpItem(TEST_PREF_KEY, TEST_DEFAULT_VALUE, isBootAware = true)
+            LauncherPrefs.backedUpItem(
+                TEST_PREF_KEY,
+                TEST_DEFAULT_VALUE,
+                EncryptionType.MOVE_TO_DEVICE_PROTECTED
+            )
 
         val bootAwarePrefs: SharedPreferences =
             context
@@ -254,7 +267,11 @@
     @Test
     fun remove_bootAwareItem_removesFromEncryptedStorage() {
         val bootAwareItem =
-            LauncherPrefs.backedUpItem(TEST_PREF_KEY, TEST_DEFAULT_VALUE, isBootAware = true)
+            LauncherPrefs.backedUpItem(
+                TEST_PREF_KEY,
+                TEST_DEFAULT_VALUE,
+                EncryptionType.MOVE_TO_DEVICE_PROTECTED
+            )
 
         val encryptedPrefs: SharedPreferences =
             context.getSharedPreferences(bootAwareItem.sharedPrefFile, Context.MODE_PRIVATE)
@@ -271,7 +288,11 @@
     @Test
     fun migrate_bootAwareItemsToDeviceProtectedStorage_worksAsIntended() {
         val bootAwareItem =
-            LauncherPrefs.backedUpItem(TEST_PREF_KEY, TEST_DEFAULT_VALUE, isBootAware = true)
+            LauncherPrefs.backedUpItem(
+                TEST_PREF_KEY,
+                TEST_DEFAULT_VALUE,
+                EncryptionType.MOVE_TO_DEVICE_PROTECTED
+            )
         launcherPrefs.removeSync(bootAwareItem)
 
         val bootAwarePrefs: SharedPreferences =
@@ -303,7 +324,7 @@
             LauncherPrefs.backedUpItem(
                 TEST_PREF_KEY + "_",
                 TEST_DEFAULT_VALUE + "_",
-                isBootAware = false
+                EncryptionType.ENCRYPTED
             )
 
         val bootAwarePrefs: SharedPreferences =
diff --git a/tests/src/com/android/launcher3/allapps/OopTaplOpenCloseAllApps.java b/tests/src/com/android/launcher3/allapps/TaplOpenCloseAllApps.java
similarity index 84%
rename from tests/src/com/android/launcher3/allapps/OopTaplOpenCloseAllApps.java
rename to tests/src/com/android/launcher3/allapps/TaplOpenCloseAllApps.java
index f9dadaa..39dbcb2 100644
--- a/tests/src/com/android/launcher3/allapps/OopTaplOpenCloseAllApps.java
+++ b/tests/src/com/android/launcher3/allapps/TaplOpenCloseAllApps.java
@@ -21,11 +21,17 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
+import android.content.Intent;
 import android.platform.test.annotations.PlatinumTest;
 
+import androidx.test.filters.FlakyTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.tapl.AllApps;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
@@ -37,7 +43,10 @@
  * 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 OopTaplOpenCloseAllApps extends AbstractLauncherUiTest {
+public class TaplOpenCloseAllApps extends AbstractLauncherUiTest {
+
+    public static final String READ_DEVICE_CONFIG_PERMISSION =
+            "android.permission.READ_DEVICE_CONFIG";
 
     /**
      * Calls static method initialize
@@ -188,4 +197,24 @@
             allApps.unfreeze();
         }
     }
+
+    /**
+     * Makes sure that when pressing back when AllApps is open we go back to the Home screen.
+     */
+    @FlakyTest(bugId = 256615483)
+    @Test
+    @PortraitLandscape
+    public void testPressBackFromAllAppsToHome() {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+                READ_DEVICE_CONFIG_PERMISSION);
+        assumeFalse(FeatureFlags.ENABLE_BACK_SWIPE_LAUNCHER_ANIMATION.get());
+        mLauncher.getWorkspace().switchToAllApps();
+        mLauncher.pressBack();
+        mLauncher.getWorkspace();
+        waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
+        startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
+        mLauncher.pressBack();
+        mLauncher.getWorkspace();
+        waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
+    }
 }
diff --git a/tests/src/com/android/launcher3/allapps/TaplTestsAllAppsIconsWorking.java b/tests/src/com/android/launcher3/allapps/TaplTestsAllAppsIconsWorking.java
new file mode 100644
index 0000000..fd4619e
--- /dev/null
+++ b/tests/src/com/android/launcher3/allapps/TaplTestsAllAppsIconsWorking.java
@@ -0,0 +1,66 @@
+/*
+ * 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.allapps;
+
+import static com.android.launcher3.ui.TaplTestsLauncher3.initialize;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.tapl.AppIcon;
+import com.android.launcher3.tapl.HomeAllApps;
+import com.android.launcher3.ui.AbstractLauncherUiTest;
+import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * 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 {
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        initialize(this);
+    }
+
+    /**
+     * Makes sure we can launch an icon from All apps
+     */
+    @Test
+    @PortraitLandscape
+    public void testAppIconLaunchFromAllAppsFromHome() {
+        final HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
+        assertTrue("Launcher internal state is not All Apps",
+                isInState(() -> LauncherState.ALL_APPS));
+
+        allApps.freeze();
+        try {
+            final AppIcon app = allApps.getAppIcon("TestActivity7");
+            assertNotNull("AppIcon.launch returned null", app.launch(getAppPackageName()));
+            executeOnLauncher(launcher -> assertTrue(
+                    "Launcher activity is the top activity; expecting another activity to be the "
+                            + "top one",
+                    isInLaunchedApp(launcher)));
+        } finally {
+            allApps.unfreeze();
+        }
+    }
+}
diff --git a/tests/src/com/android/launcher3/appiconmenu/TaplAppIconMenuTest.java b/tests/src/com/android/launcher3/appiconmenu/TaplAppIconMenuTest.java
index 85cf52e..0f5d85b 100644
--- a/tests/src/com/android/launcher3/appiconmenu/TaplAppIconMenuTest.java
+++ b/tests/src/com/android/launcher3/appiconmenu/TaplAppIconMenuTest.java
@@ -15,7 +15,7 @@
  */
 package com.android.launcher3.appiconmenu;
 
-import static com.android.launcher3.ui.TaplTestsLauncher3.APP_NAME;
+import static com.android.launcher3.util.TestConstants.AppNames.TEST_APP_NAME;
 import static com.android.launcher3.ui.TaplTestsLauncher3.initialize;
 
 import static org.junit.Assert.assertEquals;
@@ -64,7 +64,7 @@
         final AllApps allApps = mLauncher.getWorkspace().switchToAllApps();
         allApps.freeze();
         try {
-            final AppIconMenu menu = allApps.getAppIcon(APP_NAME).openDeepShortcutMenu();
+            final AppIconMenu menu = allApps.getAppIcon(TEST_APP_NAME).openDeepShortcutMenu();
 
             executeOnLauncher(
                     launcher -> assertTrue("Launcher internal state didn't switch to Showing Menu",
@@ -89,9 +89,9 @@
         final HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
         allApps.freeze();
         try {
-            allApps.getAppIcon(APP_NAME).dragToWorkspace(false, false);
+            allApps.getAppIcon(TEST_APP_NAME).dragToWorkspace(false, false);
             final AppIconMenu menu = mLauncher.getWorkspace().getWorkspaceAppIcon(
-                    APP_NAME).openDeepShortcutMenu();
+                    TEST_APP_NAME).openDeepShortcutMenu();
 
             executeOnLauncher(
                     launcher -> assertTrue("Launcher internal state didn't switch to Showing Menu",
diff --git a/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java b/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java
index 032a7b4..c5d5de8 100644
--- a/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java
+++ b/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java
@@ -27,6 +27,7 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator;
+import com.android.launcher3.util.rule.ViewCaptureRule;
 
 import org.junit.After;
 import org.junit.Test;
@@ -95,6 +96,7 @@
     }
 
     @Test
+    @ViewCaptureRule.MayProduceNoFrames
     public void testPromiseIcon_notAddedFromIneligibleSession() throws Throwable {
         final String appLabel = "Test Promise App " + UUID.randomUUID().toString();
         final ItemOperator findPromiseApp = (info, view) ->
diff --git a/tests/src/com/android/launcher3/dragging/TaplDragTest.java b/tests/src/com/android/launcher3/dragging/TaplDragTest.java
index c652b98..7ec7826 100644
--- a/tests/src/com/android/launcher3/dragging/TaplDragTest.java
+++ b/tests/src/com/android/launcher3/dragging/TaplDragTest.java
@@ -15,10 +15,10 @@
  */
 package com.android.launcher3.dragging;
 
-import static com.android.launcher3.ui.TaplTestsLauncher3.APP_NAME;
-import static com.android.launcher3.ui.TaplTestsLauncher3.GMAIL_APP_NAME;
-import static com.android.launcher3.ui.TaplTestsLauncher3.MAPS_APP_NAME;
-import static com.android.launcher3.ui.TaplTestsLauncher3.STORE_APP_NAME;
+import static com.android.launcher3.util.TestConstants.AppNames.TEST_APP_NAME;
+import static com.android.launcher3.util.TestConstants.AppNames.GMAIL_APP_NAME;
+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.TaplTestsLauncher3.initialize;
 
 import static org.junit.Assert.assertEquals;
@@ -118,7 +118,7 @@
         allApps.freeze();
         try {
             final HomeAppIconMenuItem menuItem = allApps
-                    .getAppIcon(APP_NAME)
+                    .getAppIcon(TEST_APP_NAME)
                     .openDeepShortcutMenu()
                     .getMenuItem(0);
             final String actualShortcutName = menuItem.getText();
@@ -147,7 +147,7 @@
             final HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
             allApps.freeze();
             try {
-                allApps.getAppIcon(APP_NAME)
+                allApps.getAppIcon(TEST_APP_NAME)
                         .openDeepShortcutMenu()
                         .getMenuItem(0)
                         .dragToWorkspace(target.x, target.y);
@@ -194,8 +194,8 @@
         final HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
         allApps.freeze();
         try {
-            allApps.getAppIcon(APP_NAME).dragToWorkspace(false, false);
-            mLauncher.getWorkspace().getWorkspaceAppIcon(APP_NAME).launch(getAppPackageName());
+            allApps.getAppIcon(TEST_APP_NAME).dragToWorkspace(false, false);
+            mLauncher.getWorkspace().getWorkspaceAppIcon(TEST_APP_NAME).launch(getAppPackageName());
         } finally {
             allApps.unfreeze();
         }
@@ -222,7 +222,7 @@
             final HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
             allApps.freeze();
             try {
-                allApps.getAppIcon(APP_NAME).dragToWorkspace(target.x, target.y);
+                allApps.getAppIcon(TEST_APP_NAME).dragToWorkspace(target.x, target.y);
             } finally {
                 allApps.unfreeze();
             }
@@ -235,7 +235,7 @@
         }
 
         // test to move a shortcut to other cell.
-        final HomeAppIcon launcherTestAppIcon = createShortcutInCenterIfNotExist(APP_NAME);
+        final HomeAppIcon launcherTestAppIcon = createShortcutInCenterIfNotExist(TEST_APP_NAME);
         for (Point target : targets) {
             startTime = SystemClock.uptimeMillis();
             launcherTestAppIcon.dragToWorkspace(target.x, target.y);
diff --git a/tests/src/com/android/launcher3/dragging/TaplUninstallRemove.java b/tests/src/com/android/launcher3/dragging/TaplUninstallRemove.java
index 712806c..7027e85 100644
--- a/tests/src/com/android/launcher3/dragging/TaplUninstallRemove.java
+++ b/tests/src/com/android/launcher3/dragging/TaplUninstallRemove.java
@@ -16,11 +16,12 @@
 package com.android.launcher3.dragging;
 
 import static com.android.launcher3.testing.shared.TestProtocol.ICON_MISSING;
-import static com.android.launcher3.ui.TaplTestsLauncher3.APP_NAME;
-import static com.android.launcher3.ui.TaplTestsLauncher3.DUMMY_APP_NAME;
-import static com.android.launcher3.ui.TaplTestsLauncher3.MAPS_APP_NAME;
-import static com.android.launcher3.ui.TaplTestsLauncher3.STORE_APP_NAME;
 import static com.android.launcher3.ui.TaplTestsLauncher3.initialize;
+import static com.android.launcher3.util.TestConstants.AppNames.DUMMY_APP_NAME;
+import static com.android.launcher3.util.TestConstants.AppNames.GMAIL_APP_NAME;
+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.util.TestConstants.AppNames.TEST_APP_NAME;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -45,7 +46,7 @@
 
 /**
  * Test runs in Out of process (Oop) and In process (Ipc)
- * Test the behaviour of uninstalling and removing apps both from AllApps and from the Workspace.
+ * Test the behaviour of uninstalling and removing apps both from AllApps, Workspace and Hotseat.
  */
 public class TaplUninstallRemove extends AbstractLauncherUiTest {
 
@@ -62,7 +63,7 @@
     @Test
     @PortraitLandscape
     public void testDeleteFromWorkspace() {
-        for (String appName : new String[]{"Gmail", "Play Store", APP_NAME}) {
+        for (String appName : new String[]{GMAIL_APP_NAME, STORE_APP_NAME, TEST_APP_NAME}) {
             final HomeAppIcon homeAppIcon = createShortcutInCenterIfNotExist(appName);
             Workspace workspace = mLauncher.getWorkspace().deleteAppIcon(homeAppIcon);
             workspace.verifyWorkspaceAppIconIsGone(
@@ -164,4 +165,19 @@
             TestUtil.uninstallDummyApp();
         }
     }
+
+    /**
+     * Drag icon from the Hotseat to the delete drop target
+     */
+    @Test
+    @PortraitLandscape
+    public void testAddDeleteShortcutOnHotseat() {
+        mLauncher.getWorkspace()
+                .deleteAppIcon(mLauncher.getWorkspace().getHotseatAppIcon(0))
+                .switchToAllApps()
+                .getAppIcon(TEST_APP_NAME)
+                .dragToHotseat(0);
+        mLauncher.getWorkspace().deleteAppIcon(
+                mLauncher.getWorkspace().getHotseatAppIcon(TEST_APP_NAME));
+    }
 }
diff --git a/tests/src/com/android/launcher3/icons/FastBitmapDrawableTest.java b/tests/src/com/android/launcher3/icons/FastBitmapDrawableTest.java
index 038c98b..fbbfb2a 100644
--- a/tests/src/com/android/launcher3/icons/FastBitmapDrawableTest.java
+++ b/tests/src/com/android/launcher3/icons/FastBitmapDrawableTest.java
@@ -31,6 +31,7 @@
 import static org.mockito.Mockito.when;
 
 import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
 import android.view.animation.AccelerateInterpolator;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.PathInterpolator;
@@ -42,6 +43,8 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 import org.mockito.Spy;
 
 /**
@@ -56,9 +59,11 @@
     @Spy
     FastBitmapDrawable mFastBitmapDrawable =
             spy(new FastBitmapDrawable(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888)));
+    @Mock Drawable mBadge;
 
     @Before
     public void setUp() {
+        MockitoAnnotations.initMocks(this);
         FastBitmapDrawable.setFlagHoverEnabled(true);
         when(mFastBitmapDrawable.isVisible()).thenReturn(true);
         mFastBitmapDrawable.mIsPressed = false;
@@ -326,4 +331,15 @@
         assertEquals("End value not correct.", (float) SCALE.get(mFastBitmapDrawable), 1f, EPSILON);
         verify(mFastBitmapDrawable).invalidateSelf();
     }
+
+    @Test
+    public void testUpdateBadgeAlpha() {
+        mFastBitmapDrawable.setBadge(mBadge);
+
+        mFastBitmapDrawable.setAlpha(1);
+        mFastBitmapDrawable.setAlpha(0);
+
+        verify(mBadge).setAlpha(1);
+        verify(mBadge).setAlpha(0);
+    }
 }
diff --git a/tests/src/com/android/launcher3/logging/StartupLatencyLoggerTest.kt b/tests/src/com/android/launcher3/logging/StartupLatencyLoggerTest.kt
index fffa6d7..a29218c 100644
--- a/tests/src/com/android/launcher3/logging/StartupLatencyLoggerTest.kt
+++ b/tests/src/com/android/launcher3/logging/StartupLatencyLoggerTest.kt
@@ -357,6 +357,7 @@
         assertThat(underTest.startTimeByEvent.size()).isEqualTo(4)
         assertThat(underTest.endTimeByEvent.size()).isEqualTo(4)
         assertThat(underTest.cardinality).isEqualTo(235)
+        assertThat(underTest.isTornDown).isFalse()
 
         underTest.reset()
 
@@ -364,5 +365,26 @@
         assertThat(underTest.endTimeByEvent.isEmpty()).isTrue()
         assertThat(underTest.cardinality).isEqualTo(StartupLatencyLogger.UNSET_INT)
         assertThat(underTest.workspaceLoadStartTime).isEqualTo(StartupLatencyLogger.UNSET_LONG)
+        assertThat(underTest.isTornDown).isTrue()
+    }
+
+    @Test
+    @UiThreadTest
+    fun tornDown_rejectLogs() {
+        underTest.reset()
+
+        underTest
+            .logStart(
+                StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION,
+                100
+            )
+            .logEnd(
+                StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION,
+                200
+            )
+            .logCardinality(123)
+        assertThat(underTest.startTimeByEvent.isEmpty()).isTrue()
+        assertThat(underTest.endTimeByEvent.isEmpty()).isTrue()
+        assertThat(underTest.cardinality).isEqualTo(StartupLatencyLogger.UNSET_INT)
     }
 }
diff --git a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
index 1155227..78c61d5 100644
--- a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
+++ b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
@@ -24,20 +24,19 @@
 import com.android.launcher3.util.Executors
 import com.android.launcher3.util.IntArray
 import com.android.launcher3.util.TestUtil.runOnExecutorSync
-import com.android.launcher3.util.any
-import com.android.launcher3.util.eq
-import com.android.launcher3.util.same
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
 import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.Mockito.`when` as whenever
-import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.same
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.whenever
 
 /** Tests for [AddWorkspaceItemsTask] */
 @SmallTest
@@ -46,12 +45,11 @@
 
     private lateinit var mDataModelCallbacks: MyCallbacks
 
-    @Mock private lateinit var mWorkspaceItemSpaceFinder: WorkspaceItemSpaceFinder
+    private val mWorkspaceItemSpaceFinder: WorkspaceItemSpaceFinder = mock()
 
     @Before
     override fun setup() {
         super.setup()
-        MockitoAnnotations.initMocks(this)
         mDataModelCallbacks = MyCallbacks()
         Executors.MAIN_EXECUTOR.submit { mModelHelper.model.addCallbacks(mDataModelCallbacks) }
             .get()
diff --git a/tests/src/com/android/launcher3/model/FactitiousDbController.kt b/tests/src/com/android/launcher3/model/FactitiousDbController.kt
new file mode 100644
index 0000000..664f23e
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/FactitiousDbController.kt
@@ -0,0 +1,64 @@
+package com.android.launcher3.model
+
+import android.content.Context
+import android.database.Cursor
+import android.database.sqlite.SQLiteDatabase
+import androidx.test.platform.app.InstrumentationRegistry
+import java.io.BufferedReader
+import java.io.InputStreamReader
+
+private val All_COLUMNS =
+    arrayOf(
+        "_id",
+        "title",
+        "intent",
+        "container",
+        "screen",
+        "cellX",
+        "cellY",
+        "spanX",
+        "spanY",
+        "itemType",
+        "appWidgetId",
+        "icon",
+        "appWidgetProvider",
+        "modified",
+        "restored",
+        "profileId",
+        "rank",
+        "options",
+        "appWidgetSource"
+    )
+private const val INSERTION_STATEMENT_FILE = "databases/workspace_items.sql"
+
+class FactitiousDbController(context: Context) : ModelDbController(context) {
+
+    private val inMemoryDb: SQLiteDatabase by lazy {
+        SQLiteDatabase.createInMemory(SQLiteDatabase.OpenParams.Builder().build()).also { db ->
+            BufferedReader(
+                    InputStreamReader(
+                        InstrumentationRegistry.getInstrumentation()
+                            .context
+                            .assets
+                            .open(INSERTION_STATEMENT_FILE)
+                    )
+                )
+                .lines()
+                .forEach { sqlStatement -> db.execSQL(sqlStatement) }
+        }
+    }
+
+    override fun query(
+        table: String,
+        projection: Array<out String>?,
+        selection: String?,
+        selectionArgs: Array<out String>?,
+        sortOrder: String?
+    ): Cursor {
+        return inMemoryDb.query(table, All_COLUMNS, selection, selectionArgs, null, null, sortOrder)
+    }
+
+    override fun loadDefaultFavoritesIfNecessary() {
+        // No-Op
+    }
+}
diff --git a/tests/src/com/android/launcher3/model/LoaderCursorTest.java b/tests/src/com/android/launcher3/model/LoaderCursorTest.java
index 544ed6b..389ec5c 100644
--- a/tests/src/com/android/launcher3/model/LoaderCursorTest.java
+++ b/tests/src/com/android/launcher3/model/LoaderCursorTest.java
@@ -175,7 +175,7 @@
 
         // Item outside screen bounds are not placed
         assertFalse(mLoaderCursor.checkItemPlacement(
-                newItemInfo(4, 4, 1, 1, CONTAINER_DESKTOP, 1)));
+                newItemInfo(4, 4, 1, 1, CONTAINER_DESKTOP, 1), true));
     }
 
     @Test
@@ -186,22 +186,22 @@
 
         // Overlapping mItems are not placed
         assertTrue(mLoaderCursor.checkItemPlacement(
-                newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1)));
+                newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1), true));
         assertFalse(mLoaderCursor.checkItemPlacement(
-                newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1)));
+                newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1), true));
 
         assertTrue(mLoaderCursor.checkItemPlacement(
-                newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2)));
+                newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2), true));
         assertFalse(mLoaderCursor.checkItemPlacement(
-                newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2)));
+                newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2), true));
 
         assertTrue(mLoaderCursor.checkItemPlacement(
-                newItemInfo(1, 1, 1, 1, CONTAINER_DESKTOP, 1)));
+                newItemInfo(1, 1, 1, 1, CONTAINER_DESKTOP, 1), true));
         assertTrue(mLoaderCursor.checkItemPlacement(
-                newItemInfo(2, 2, 2, 2, CONTAINER_DESKTOP, 1)));
+                newItemInfo(2, 2, 2, 2, CONTAINER_DESKTOP, 1), true));
 
         assertFalse(mLoaderCursor.checkItemPlacement(
-                newItemInfo(3, 2, 1, 2, CONTAINER_DESKTOP, 1)));
+                newItemInfo(3, 2, 1, 2, CONTAINER_DESKTOP, 1), true));
     }
 
     @Test
@@ -212,12 +212,12 @@
 
         // Hotseat mItems are only placed based on screenId
         assertTrue(mLoaderCursor.checkItemPlacement(
-                newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 1)));
+                newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 1), true));
         assertTrue(mLoaderCursor.checkItemPlacement(
-                newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 2)));
+                newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 2), true));
 
         assertFalse(mLoaderCursor.checkItemPlacement(
-                newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 3)));
+                newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 3), true));
     }
 
     private ItemInfo newItemInfo(int cellX, int cellY, int spanX, int spanY,
diff --git a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
new file mode 100644
index 0000000..1421087
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
@@ -0,0 +1,104 @@
+package com.android.launcher3.model
+
+import android.appwidget.AppWidgetManager
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.LauncherModel
+import com.android.launcher3.LauncherModel.LoaderTransaction
+import com.android.launcher3.icons.IconCache
+import com.android.launcher3.icons.cache.CachingLogic
+import com.android.launcher3.icons.cache.IconCacheUpdateHandler
+import com.android.launcher3.util.Executors.MODEL_EXECUTOR
+import com.android.launcher3.util.LooperIdleLock
+import com.google.common.truth.Truth
+import java.util.concurrent.CountDownLatch
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class LoaderTaskTest {
+    @Mock private lateinit var app: LauncherAppState
+    @Mock private lateinit var bgAllAppsList: AllAppsList
+    @Mock private lateinit var modelDelegate: ModelDelegate
+    @Mock private lateinit var launcherBinder: LauncherBinder
+    @Mock private lateinit var launcherModel: LauncherModel
+    @Mock private lateinit var transaction: LoaderTransaction
+    @Mock private lateinit var iconCache: IconCache
+    @Mock private lateinit var idleLock: LooperIdleLock
+    @Mock private lateinit var iconCacheUpdateHandler: IconCacheUpdateHandler
+    @Mock private lateinit var appWidgetManager: AppWidgetManager
+
+    @Before
+    fun setup() {
+        val context = InstrumentationRegistry.getInstrumentation().targetContext
+        val idp =
+            InvariantDeviceProfile.INSTANCE[context].apply {
+                numRows = 5
+                numColumns = 6
+                numDatabaseHotseatIcons = 5
+            }
+
+        MockitoAnnotations.initMocks(this)
+        `when`(app.context).thenReturn(context)
+        `when`(app.model).thenReturn(launcherModel)
+        `when`(launcherModel.beginLoader(any(LoaderTask::class.java))).thenReturn(transaction)
+        `when`(app.iconCache).thenReturn(iconCache)
+        `when`(launcherModel.modelDbController).thenReturn(FactitiousDbController(context))
+        `when`(app.invariantDeviceProfile).thenReturn(idp)
+        `when`(launcherBinder.newIdleLock(any(LoaderTask::class.java))).thenReturn(idleLock)
+        `when`(idleLock.awaitLocked(1000)).thenReturn(false)
+        `when`(iconCache.updateHandler).thenReturn(iconCacheUpdateHandler)
+        `when`(appWidgetManager.getInstalledProvidersForProfile(any(UserHandle::class.java)))
+            .thenReturn(emptyList())
+    }
+
+    @Test
+    fun loadsDataProperly() =
+        with(BgDataModel()) {
+            LoaderTask(app, bgAllAppsList, this, modelDelegate, launcherBinder)
+                .runSyncOnBackgroundThread()
+            Truth.assertThat(workspaceItems.size).isAtLeast(25)
+            Truth.assertThat(appWidgets.size).isAtLeast(7)
+            Truth.assertThat(folders.size()).isAtLeast(8)
+            Truth.assertThat(itemsIdMap.size()).isAtLeast(40)
+        }
+
+    @Test
+    fun bindsLoadedDataCorrectly() {
+        LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder)
+            .runSyncOnBackgroundThread()
+
+        verify(launcherBinder).bindWorkspace(true, false)
+        verify(modelDelegate).workspaceLoadComplete()
+        verify(modelDelegate).loadAndBindAllAppsItems(any(), any(), any())
+        verify(launcherBinder).bindAllApps()
+        verify(iconCacheUpdateHandler, times(4)).updateIcons(any(), any<CachingLogic<Any>>(), any())
+        verify(launcherBinder).bindDeepShortcuts()
+        verify(launcherBinder).bindWidgets()
+        verify(modelDelegate).loadAndBindOtherItems(any())
+        verify(iconCacheUpdateHandler).finish()
+        verify(modelDelegate).modelLoadComplete()
+        verify(transaction).commit()
+    }
+}
+
+private fun LoaderTask.runSyncOnBackgroundThread() {
+    val latch = CountDownLatch(1)
+    MODEL_EXECUTOR.execute {
+        run()
+        latch.countDown()
+    }
+    latch.await()
+}
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index e837b8b..34ebe11 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -183,8 +183,8 @@
         if (TestHelpers.isInLauncherProcess()) {
             Utilities.enableRunningInTestHarnessForTests();
             mLauncher.setSystemHealthSupplier(startTime -> TestCommandReceiver.callCommand(
-                    TestCommandReceiver.GET_SYSTEM_HEALTH_MESSAGE, startTime.toString()).
-                    getString("result"));
+                            TestCommandReceiver.GET_SYSTEM_HEALTH_MESSAGE, startTime.toString())
+                            .getString("result"));
             mLauncher.setOnSettledStateAction(
                     containerType -> executeOnLauncher(
                             launcher ->
@@ -207,7 +207,7 @@
         final SimpleBroadcastReceiver broadcastReceiver =
                 new SimpleBroadcastReceiver(i -> count.countDown());
         broadcastReceiver.registerPkgActions(mTargetContext, pkg,
-                        Intent.ACTION_PACKAGE_RESTARTED, Intent.ACTION_PACKAGE_DATA_CLEARED);
+                Intent.ACTION_PACKAGE_RESTARTED, Intent.ACTION_PACKAGE_DATA_CLEARED);
 
         mDevice.executeShellCommand("pm clear " + pkg);
         assertTrue(pkg + " didn't restart", count.await(10, TimeUnit.SECONDS));
@@ -242,8 +242,6 @@
     public void setUp() throws Exception {
         mLauncher.onTestStart();
 
-        verifyKeyguardInvisible();
-
         final String launcherPackageName = mDevice.getLauncherPackageName();
         try {
             final Context context = InstrumentationRegistry.getContext();
@@ -273,9 +271,12 @@
                 }
             }
         }
+
+        verifyKeyguardInvisible();
     }
 
-    private static void verifyKeyguardInvisible() {
+    /** Fail if lock screen is present */
+    public static void verifyKeyguardInvisible() {
         final boolean keyguardAlreadyVisible = sSeenKeyguard;
 
         sSeenKeyguard = sSeenKeyguard
@@ -344,6 +345,15 @@
         });
     }
 
+    // Execute an action on Launcher, but forgive it when launcher is null.
+    // Launcher can be null if teardown is happening after a failed setup step where launcher
+    // activity failed to be created.
+    protected void executeOnLauncherInTearDown(Consumer<Launcher> f) {
+        executeOnLauncher(launcher -> {
+            if (launcher != null) f.accept(launcher);
+        });
+    }
+
     // Cannot be used in TaplTests between a Tapl call injecting a gesture and a tapl call
     // expecting the results of that gesture because the wait can hide flakeness.
     protected void waitForState(String message, Supplier<LauncherState> state) {
diff --git a/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java b/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java
index 2cdcf24..6c2950c 100644
--- a/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java
+++ b/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java
@@ -20,21 +20,30 @@
 
 import static com.android.launcher3.BubbleTextView.DISPLAY_ALL_APPS;
 import static com.android.launcher3.BubbleTextView.DISPLAY_PREDICTION_ROW;
+import static com.android.launcher3.BubbleTextView.DISPLAY_SEARCH_RESULT;
+import static com.android.launcher3.BubbleTextView.DISPLAY_SEARCH_RESULT_SMALL;
+
+import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.graphics.Typeface;
 import android.platform.test.flag.junit.SetFlagsRule;
+import android.os.UserHandle;
 import android.view.ViewGroup;
 
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.Flags;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.search.StringMatcherUtility;
 import com.android.launcher3.util.ActivityContextWrapper;
+import com.android.launcher3.util.FlagOp;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.views.BaseDragLayer;
 
@@ -55,6 +64,8 @@
     @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
     private static final StringMatcherUtility.StringMatcher
             MATCHER = StringMatcherUtility.StringMatcher.getInstance();
+    private static final UserHandle WORK_HANDLE = new UserHandle(13);
+    private static final int WORK_FLAG = 1;
     private static final int ONE_LINE = 1;
     private static final int TWO_LINE = 2;
     private static final String TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT = "Battery Stats";
@@ -81,6 +92,7 @@
     private ItemInfoWithIcon mItemInfoWithIcon;
     private Context mContext;
     private int mLimitedWidth;
+    private AppInfo mGmailAppInfo;
 
     @Before
     public void setUp() throws Exception {
@@ -110,6 +122,9 @@
                 return null;
             }
         };
+        ComponentName componentName = new ComponentName(mContext,
+                "com.android.launcher3.tests.Activity" + "Gmail");
+        mGmailAppInfo = new AppInfo(componentName, "Gmail", WORK_HANDLE, new Intent());
     }
 
     @Test
@@ -359,4 +374,28 @@
 
         assertEquals(TWO_LINE, mBubbleTextView.getLineCount());
     }
+
+    @Test
+    public void applyIconAndLabel_whenDisplay_DISPLAY_SEARCH_RESULT_SMALL_noBadge() {
+        FlagOp op = FlagOp.NO_OP;
+        // apply the WORK bitmap flag to show work badge
+        mGmailAppInfo.bitmap.flags = op.apply(WORK_FLAG);
+        mBubbleTextView.setDisplay(DISPLAY_SEARCH_RESULT_SMALL);
+
+        mBubbleTextView.applyIconAndLabel(mGmailAppInfo);
+
+        assertThat(mBubbleTextView.getIcon().hasBadge()).isEqualTo(false);
+    }
+
+    @Test
+    public void applyIconAndLabel_whenDisplay_DISPLAY_SEARCH_RESULT_hasBadge() {
+        FlagOp op = FlagOp.NO_OP;
+        // apply the WORK bitmap flag to show work badge
+        mGmailAppInfo.bitmap.flags = op.apply(WORK_FLAG);
+        mBubbleTextView.setDisplay(DISPLAY_SEARCH_RESULT);
+
+        mBubbleTextView.applyIconAndLabel(mGmailAppInfo);
+
+        assertThat(mBubbleTextView.getIcon().hasBadge()).isEqualTo(true);
+    }
 }
diff --git a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
index ad11268..b8ca43f 100644
--- a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
+++ b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
@@ -74,7 +74,7 @@
             private void evaluateInPortrait() throws Throwable {
                 mTest.mDevice.setOrientationNatural();
                 mTest.mLauncher.setExpectedRotation(Surface.ROTATION_0);
-                AbstractLauncherUiTest.checkDetectedLeaks(mTest.mLauncher);
+                AbstractLauncherUiTest.checkDetectedLeaks(mTest.mLauncher, true);
                 base.evaluate();
                 mTest.getDevice().pressHome();
             }
@@ -82,7 +82,7 @@
             private void evaluateInLandscape() throws Throwable {
                 mTest.mDevice.setOrientationLeft();
                 mTest.mLauncher.setExpectedRotation(Surface.ROTATION_90);
-                AbstractLauncherUiTest.checkDetectedLeaks(mTest.mLauncher);
+                AbstractLauncherUiTest.checkDetectedLeaks(mTest.mLauncher, true);
                 base.evaluate();
                 mTest.getDevice().pressHome();
             }
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 9aaca54..f2cbd92 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -16,54 +16,21 @@
 
 package com.android.launcher3.ui;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeFalse;
 
-import android.content.Intent;
-import android.platform.test.annotations.PlatinumTest;
-
-import androidx.test.filters.FlakyTest;
 import androidx.test.filters.LargeTest;
-import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.tapl.AllApps;
-import com.android.launcher3.tapl.AppIcon;
-import com.android.launcher3.tapl.HomeAllApps;
-import com.android.launcher3.tapl.HomeAppIcon;
-import com.android.launcher3.tapl.Workspace;
-import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
-import com.android.launcher3.util.LauncherLayoutBuilder;
-import com.android.launcher3.util.TestUtil;
-import com.android.launcher3.util.rule.TISBindRule;
 
-import org.junit.After;
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @LargeTest
 @RunWith(AndroidJUnit4.class)
 public class TaplTestsLauncher3 extends AbstractLauncherUiTest {
-    public static final String APP_NAME = "LauncherTestApp";
-    public static final String DUMMY_APP_NAME = "Aardwolf";
-    public static final String MAPS_APP_NAME = "Maps";
-    public static final String STORE_APP_NAME = "Play Store";
-    public static final String GMAIL_APP_NAME = "Gmail";
-    private static final String READ_DEVICE_CONFIG_PERMISSION =
-            "android.permission.READ_DEVICE_CONFIG";
-
-    @Rule
-    public TISBindRule mTISBindRule = new TISBindRule();
-
-    private AutoCloseable mLauncherLayout;
 
     @Before
     public void setUp() throws Exception {
@@ -88,13 +55,6 @@
         AbstractLauncherUiTest.checkDetectedLeaks(test.mLauncher, true);
     }
 
-    @After
-    public void tearDown() throws Exception {
-        if (mLauncherLayout != null) {
-            mLauncherLayout.close();
-        }
-    }
-
     // Please don't add negative test cases for methods that fail only after a long wait.
     public static void expectFail(String message, Runnable action) {
         boolean failed = false;
@@ -106,14 +66,6 @@
         assertTrue(message, failed);
     }
 
-    public static boolean isWorkspaceScrollable(Launcher launcher) {
-        return launcher.getWorkspace().getPageCount() > launcher.getWorkspace().getPanelCount();
-    }
-
-    private int getCurrentWorkspacePage(Launcher launcher) {
-        return launcher.getWorkspace().getCurrentPage();
-    }
-
     @Test
     public void testDevicePressMenu() throws Exception {
         mDevice.pressMenu();
@@ -124,140 +76,4 @@
         // Check that pressHome works when the menu is shown.
         mLauncher.goHome();
     }
-
-    @PlatinumTest(focusArea = "launcher")
-    @Test
-    public void testWorkspace() throws Exception {
-        // Set workspace  that includes the chrome Activity app icon on the hotseat.
-        LauncherLayoutBuilder builder = new LauncherLayoutBuilder()
-                .atHotseat(0).putApp("com.android.chrome", "com.google.android.apps.chrome.Main");
-        mLauncherLayout = TestUtil.setLauncherDefaultLayout(mTargetContext, builder);
-        reinitializeLauncherData();
-
-        final Workspace workspace = mLauncher.getWorkspace();
-
-        // Test that ensureWorkspaceIsScrollable adds a page by dragging an icon there.
-        executeOnLauncher(launcher -> assertFalse("Initial workspace state is scrollable",
-                isWorkspaceScrollable(launcher)));
-        assertEquals("Initial workspace doesn't have the correct page", workspace.pagesPerScreen(),
-                workspace.getPageCount());
-        workspace.verifyWorkspaceAppIconIsGone("Chrome app was found on empty workspace", "Chrome");
-        workspace.ensureWorkspaceIsScrollable();
-
-        executeOnLauncher(
-                launcher -> assertEquals(
-                        "Ensuring workspace scrollable didn't switch to next screen",
-                        workspace.pagesPerScreen(), getCurrentWorkspacePage(launcher)));
-        executeOnLauncher(
-                launcher -> assertTrue("ensureScrollable didn't make workspace scrollable",
-                        isWorkspaceScrollable(launcher)));
-        assertNotNull("ensureScrollable didn't add Chrome app",
-                workspace.getWorkspaceAppIcon("Chrome"));
-
-        // Test flinging workspace.
-        workspace.flingBackward();
-        assertTrue("Launcher internal state is not Home", isInState(() -> LauncherState.NORMAL));
-        executeOnLauncher(
-                launcher -> assertEquals("Flinging back didn't switch workspace to page #0",
-                        0, getCurrentWorkspacePage(launcher)));
-
-        workspace.flingForward();
-        executeOnLauncher(
-                launcher -> assertEquals("Flinging forward didn't switch workspace to next screen",
-                        workspace.pagesPerScreen(), getCurrentWorkspacePage(launcher)));
-        assertTrue("Launcher internal state is not Home", isInState(() -> LauncherState.NORMAL));
-
-        // Test starting a workspace app.
-        final HomeAppIcon app = workspace.getWorkspaceAppIcon("Chrome");
-        assertNotNull("No Chrome app in workspace", app);
-    }
-
-    public static void runIconLaunchFromAllAppsTest(AbstractLauncherUiTest test, AllApps allApps) {
-        allApps.freeze();
-        try {
-            final AppIcon app = allApps.getAppIcon("TestActivity7");
-            assertNotNull("AppIcon.launch returned null", app.launch(getAppPackageName()));
-            test.executeOnLauncher(launcher -> assertTrue(
-                    "Launcher activity is the top activity; expecting another activity to be the "
-                            + "top one",
-                    test.isInLaunchedApp(launcher)));
-        } finally {
-            allApps.unfreeze();
-        }
-    }
-
-    @Test
-    @PortraitLandscape
-    public void testAppIconLaunchFromAllAppsFromHome() throws Exception {
-        final HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
-        assertTrue("Launcher internal state is not All Apps",
-                isInState(() -> LauncherState.ALL_APPS));
-
-        runIconLaunchFromAllAppsTest(this, allApps);
-    }
-
-    @FlakyTest(bugId = 256615483)
-    @Test
-    @PortraitLandscape
-    public void testPressBack() throws Exception {
-        InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
-                READ_DEVICE_CONFIG_PERMISSION);
-        assumeFalse(FeatureFlags.ENABLE_BACK_SWIPE_LAUNCHER_ANIMATION.get());
-        mLauncher.getWorkspace().switchToAllApps();
-        mLauncher.pressBack();
-        mLauncher.getWorkspace();
-        waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
-        startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
-        mLauncher.pressBack();
-        mLauncher.getWorkspace();
-        waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
-    }
-
-    @Test
-    @PortraitLandscape
-    public void testAddDeleteShortcutOnHotseat() {
-        mLauncher.getWorkspace()
-                .deleteAppIcon(mLauncher.getWorkspace().getHotseatAppIcon(0))
-                .switchToAllApps()
-                .getAppIcon(APP_NAME)
-                .dragToHotseat(0);
-        mLauncher.getWorkspace().deleteAppIcon(
-                mLauncher.getWorkspace().getHotseatAppIcon(APP_NAME));
-    }
-
-    @Test
-    public void testGetAppIconName() {
-        HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
-        allApps.freeze();
-        try {
-            // getAppIcon() already verifies that the icon is not null and is the correct icon name.
-            allApps.getAppIcon(APP_NAME);
-        } finally {
-            allApps.unfreeze();
-        }
-    }
-
-    @PlatinumTest(focusArea = "launcher")
-    @Test
-    public void testAddAndDeletePageAndFling() {
-        Workspace workspace = mLauncher.getWorkspace();
-        // Get the first app from the hotseat
-        HomeAppIcon hotSeatIcon = workspace.getHotseatAppIcon(0);
-        String appName = hotSeatIcon.getIconName();
-
-        // Add one page by dragging app to page 1.
-        workspace.dragIcon(hotSeatIcon, workspace.pagesPerScreen());
-        assertEquals("Incorrect Page count Number",
-                workspace.pagesPerScreen() * 2,
-                workspace.getPageCount());
-
-        // Delete one page by dragging app to hot seat.
-        workspace.getWorkspaceAppIcon(appName).dragToHotseat(0);
-
-        // Refresh workspace to avoid using stale container error.
-        workspace = mLauncher.getWorkspace();
-        assertEquals("Incorrect Page count Number",
-                workspace.pagesPerScreen(),
-                workspace.getPageCount());
-    }
 }
diff --git a/tests/src/com/android/launcher3/ui/WorkProfileTest.java b/tests/src/com/android/launcher3/ui/WorkProfileTest.java
index ac710fd..485ef94 100644
--- a/tests/src/com/android/launcher3/ui/WorkProfileTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkProfileTest.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.ui;
 
+import static com.android.launcher3.LauncherPrefs.WORK_EDU_STEP;
 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;
@@ -29,6 +30,7 @@
 
 import androidx.recyclerview.widget.RecyclerView.ViewHolder;
 
+import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.ActivityAllAppsContainerView;
 import com.android.launcher3.allapps.AllAppsPagedView;
@@ -89,8 +91,8 @@
 
     @After
     public void removeWorkProfile() throws Exception {
-        executeOnLauncher(launcher -> {
-            if (launcher == null || launcher.getAppsView() == null) {
+        executeOnLauncherInTearDown(launcher -> {
+            if (launcher.getAppsView() == null) {
                 return;
             }
             launcher.getAppsView().getAppsStore().disableDeferUpdates(DEFER_UPDATES_TEST);
@@ -174,7 +176,7 @@
         assumeTrue(mWorkProfileSetupSuccessful);
         waitForWorkTabSetup();
         executeOnLauncher(l -> {
-            l.getSharedPrefs().edit().putInt(WorkProfileManager.KEY_WORK_EDU_STEP, 0).commit();
+            LauncherPrefs.get(l).putSync(WORK_EDU_STEP.to(0));
             ((AllAppsPagedView) l.getAppsView().getContentView()).setCurrentPage(WORK_PAGE);
             l.getAppsView().getWorkManager().reset();
         });
diff --git a/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java b/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java
new file mode 100644
index 0000000..d776f21
--- /dev/null
+++ b/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java
@@ -0,0 +1,149 @@
+/*
+ * 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.ui.workspace;
+
+import static com.android.launcher3.ui.TaplTestsLauncher3.initialize;
+import static com.android.launcher3.util.TestConstants.AppNames.CHROME_APP_NAME;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+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;
+import com.android.launcher3.tapl.Workspace;
+import com.android.launcher3.ui.AbstractLauncherUiTest;
+import com.android.launcher3.util.LauncherLayoutBuilder;
+import com.android.launcher3.util.TestUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test the basic interactions of the Workspace, adding pages, moving the pages and removing pages.
+ */
+public class TaplWorkspaceTest extends AbstractLauncherUiTest {
+
+    private AutoCloseable mLauncherLayout;
+
+    private static boolean isWorkspaceScrollable(Launcher launcher) {
+        return launcher.getWorkspace().getPageCount() > launcher.getWorkspace().getPanelCount();
+    }
+
+    private int getCurrentWorkspacePage(Launcher launcher) {
+        return launcher.getWorkspace().getCurrentPage();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        initialize(this);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mLauncherLayout != null) {
+            mLauncherLayout.close();
+        }
+    }
+
+    /**
+     * Add an icon and add a page to ensure the Workspace is scrollable and also make sure we can
+     * 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.
+        LauncherLayoutBuilder builder = new LauncherLayoutBuilder()
+                .atHotseat(0).putApp("com.android.chrome", "com.google.android.apps.chrome.Main");
+        mLauncherLayout = TestUtil.setLauncherDefaultLayout(mTargetContext, builder);
+        reinitializeLauncherData();
+
+        final Workspace workspace = mLauncher.getWorkspace();
+
+        // Test that ensureWorkspaceIsScrollable adds a page by dragging an icon there.
+        executeOnLauncher(launcher -> assertFalse("Initial workspace state is scrollable",
+                isWorkspaceScrollable(launcher)));
+        assertEquals("Initial workspace doesn't have the correct page", workspace.pagesPerScreen(),
+                workspace.getPageCount());
+        workspace.verifyWorkspaceAppIconIsGone("Chrome app was found on empty workspace",
+                CHROME_APP_NAME);
+        workspace.ensureWorkspaceIsScrollable();
+
+        executeOnLauncher(
+                launcher -> assertEquals(
+                        "Ensuring workspace scrollable didn't switch to next screen",
+                        workspace.pagesPerScreen(), getCurrentWorkspacePage(launcher)));
+        executeOnLauncher(
+                launcher -> assertTrue("ensureScrollable didn't make workspace scrollable",
+                        isWorkspaceScrollable(launcher)));
+        assertNotNull("ensureScrollable didn't add Chrome app",
+                workspace.getWorkspaceAppIcon(CHROME_APP_NAME));
+
+        // Test flinging workspace.
+        workspace.flingBackward();
+        assertTrue("Launcher internal state is not Home", isInState(() -> LauncherState.NORMAL));
+        executeOnLauncher(
+                launcher -> assertEquals("Flinging back didn't switch workspace to page #0",
+                        0, getCurrentWorkspacePage(launcher)));
+
+        workspace.flingForward();
+        executeOnLauncher(
+                launcher -> assertEquals("Flinging forward didn't switch workspace to next screen",
+                        workspace.pagesPerScreen(), getCurrentWorkspacePage(launcher)));
+        assertTrue("Launcher internal state is not Home", isInState(() -> LauncherState.NORMAL));
+
+        // Test starting a workspace app.
+        final HomeAppIcon app = workspace.getWorkspaceAppIcon(CHROME_APP_NAME);
+        assertNotNull("No Chrome app in workspace", app);
+    }
+
+
+    /**
+     * 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();
+        // Get the first app from the hotseat
+        HomeAppIcon hotSeatIcon = workspace.getHotseatAppIcon(0);
+        String appName = hotSeatIcon.getIconName();
+
+        // Add one page by dragging app to page 1.
+        workspace.dragIcon(hotSeatIcon, workspace.pagesPerScreen());
+        assertEquals("Incorrect Page count Number",
+                workspace.pagesPerScreen() * 2,
+                workspace.getPageCount());
+
+        // Delete one page by dragging app to hot seat.
+        workspace.getWorkspaceAppIcon(appName).dragToHotseat(0);
+
+        // Refresh workspace to avoid using stale container error.
+        workspace = mLauncher.getWorkspace();
+        assertEquals("Incorrect Page count Number",
+                workspace.pagesPerScreen(),
+                workspace.getPageCount());
+    }
+}
diff --git a/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java b/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java
index 8e5e9cc..34c7707 100644
--- a/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.ui.workspace;
 
+import static com.android.launcher3.util.TestConstants.AppNames.TEST_APP_NAME;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
@@ -51,7 +53,6 @@
 public class ThemeIconsTest extends AbstractLauncherUiTest {
 
     private static final String APP_NAME = "IconThemedActivity";
-    private static final String SHORTCUT_APP_NAME = "LauncherTestApp";
     private static final String SHORTCUT_NAME = "Shortcut 1";
 
     @Test
@@ -81,7 +82,7 @@
         allApps.freeze();
 
         try {
-            HomeAppIcon icon = allApps.getAppIcon(SHORTCUT_APP_NAME);
+            HomeAppIcon icon = allApps.getAppIcon(TEST_APP_NAME);
             HomeAppIconMenuItem shortcutItem =
                     (HomeAppIconMenuItem) icon.openDeepShortcutMenu().getMenuItem(SHORTCUT_NAME);
             shortcutItem.dragToWorkspace(false, false);
@@ -118,7 +119,7 @@
         allApps.freeze();
 
         try {
-            HomeAppIcon icon = allApps.getAppIcon(SHORTCUT_APP_NAME);
+            HomeAppIcon icon = allApps.getAppIcon(TEST_APP_NAME);
             HomeAppIconMenuItem shortcutItem =
                     (HomeAppIconMenuItem) icon.openDeepShortcutMenu().getMenuItem(SHORTCUT_NAME);
             shortcutItem.dragToWorkspace(false, false);
diff --git a/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java b/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java
index 62a8179..35b4883 100644
--- a/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java
@@ -16,6 +16,11 @@
 
 package com.android.launcher3.ui.workspace;
 
+import static com.android.launcher3.util.TestConstants.AppNames.CHROME_APP_NAME;
+import static com.android.launcher3.util.TestConstants.AppNames.MAPS_APP_NAME;
+import static com.android.launcher3.util.TestConstants.AppNames.MESSAGES_APP_NAME;
+import static com.android.launcher3.util.TestConstants.AppNames.STORE_APP_NAME;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
@@ -76,14 +81,14 @@
         executeOnLauncher(launcher -> {
             launcher.enableHotseatEdu(false);
             assertPagesExist(launcher, 0, 1);
-            assertItemsOnPage(launcher, 0, "Play Store", "Maps");
+            assertItemsOnPage(launcher, 0, STORE_APP_NAME, MAPS_APP_NAME);
             assertPageEmpty(launcher, 1);
         });
     }
 
     @After
     public void tearDown() throws Exception {
-        executeOnLauncher(launcher -> launcher.enableHotseatEdu(true));
+        executeOnLauncherInTearDown(launcher -> launcher.enableHotseatEdu(true));
         if (mLauncherLayout != null) {
             mLauncherLayout.close();
         }
@@ -94,12 +99,12 @@
     public void testDragIconToRightPanel() {
         Workspace workspace = mLauncher.getWorkspace();
 
-        workspace.dragIcon(workspace.getHotseatAppIcon("Chrome"), 1);
+        workspace.dragIcon(workspace.getHotseatAppIcon(CHROME_APP_NAME), 1);
 
         executeOnLauncher(launcher -> {
             assertPagesExist(launcher, 0, 1);
-            assertItemsOnPage(launcher, 0, "Maps", "Play Store");
-            assertItemsOnPage(launcher, 1, "Chrome");
+            assertItemsOnPage(launcher, 0, MAPS_APP_NAME, STORE_APP_NAME);
+            assertItemsOnPage(launcher, 1, CHROME_APP_NAME);
         });
     }
 
@@ -108,52 +113,52 @@
     public void testSinglePageDragIconWhenMultiplePageScrollingIsPossible() {
         Workspace workspace = mLauncher.getWorkspace();
 
-        workspace.dragIcon(workspace.getHotseatAppIcon("Chrome"), 2);
+        workspace.dragIcon(workspace.getHotseatAppIcon(CHROME_APP_NAME), 2);
 
         workspace.flingBackward();
 
-        workspace.dragIcon(workspace.getWorkspaceAppIcon("Maps"), 3);
+        workspace.dragIcon(workspace.getWorkspaceAppIcon(MAPS_APP_NAME), 3);
 
         executeOnLauncher(launcher -> {
             assertPagesExist(launcher, 0, 1, 2, 3);
-            assertItemsOnPage(launcher, 0, "Play Store");
+            assertItemsOnPage(launcher, 0, STORE_APP_NAME);
             assertPageEmpty(launcher, 1);
-            assertItemsOnPage(launcher, 2, "Chrome");
-            assertItemsOnPage(launcher, 3, "Maps");
+            assertItemsOnPage(launcher, 2, CHROME_APP_NAME);
+            assertItemsOnPage(launcher, 3, MAPS_APP_NAME);
         });
 
-        workspace.dragIcon(workspace.getWorkspaceAppIcon("Maps"), 3);
+        workspace.dragIcon(workspace.getWorkspaceAppIcon(MAPS_APP_NAME), 3);
 
         executeOnLauncher(launcher -> {
             assertPagesExist(launcher, 0, 1, 2, 3, 4, 5);
-            assertItemsOnPage(launcher, 0, "Play Store");
+            assertItemsOnPage(launcher, 0, STORE_APP_NAME);
             assertPageEmpty(launcher, 1);
-            assertItemsOnPage(launcher, 2, "Chrome");
+            assertItemsOnPage(launcher, 2, CHROME_APP_NAME);
             assertPageEmpty(launcher, 3);
             assertPageEmpty(launcher, 4);
-            assertItemsOnPage(launcher, 5, "Maps");
+            assertItemsOnPage(launcher, 5, MAPS_APP_NAME);
         });
 
-        workspace.dragIcon(workspace.getWorkspaceAppIcon("Maps"), -1);
+        workspace.dragIcon(workspace.getWorkspaceAppIcon(MAPS_APP_NAME), -1);
 
         executeOnLauncher(launcher -> {
             assertPagesExist(launcher, 0, 1, 2, 3);
-            assertItemsOnPage(launcher, 0, "Play Store");
+            assertItemsOnPage(launcher, 0, STORE_APP_NAME);
             assertPageEmpty(launcher, 1);
-            assertItemsOnPage(launcher, 2, "Chrome");
-            assertItemsOnPage(launcher, 3, "Maps");
+            assertItemsOnPage(launcher, 2, CHROME_APP_NAME);
+            assertItemsOnPage(launcher, 3, MAPS_APP_NAME);
         });
 
-        workspace.dragIcon(workspace.getWorkspaceAppIcon("Maps"), -1);
+        workspace.dragIcon(workspace.getWorkspaceAppIcon(MAPS_APP_NAME), -1);
 
         workspace.flingForward();
 
-        workspace.dragIcon(workspace.getWorkspaceAppIcon("Chrome"), -2);
+        workspace.dragIcon(workspace.getWorkspaceAppIcon(CHROME_APP_NAME), -2);
 
         executeOnLauncher(launcher -> {
             assertPagesExist(launcher, 0, 1);
-            assertItemsOnPage(launcher, 0, "Chrome", "Play Store");
-            assertItemsOnPage(launcher, 1, "Maps");
+            assertItemsOnPage(launcher, 0, CHROME_APP_NAME, STORE_APP_NAME);
+            assertItemsOnPage(launcher, 1, MAPS_APP_NAME);
         });
     }
 
@@ -162,13 +167,13 @@
     public void testDragIconToPage2() {
         Workspace workspace = mLauncher.getWorkspace();
 
-        workspace.dragIcon(workspace.getWorkspaceAppIcon("Maps"), 2);
+        workspace.dragIcon(workspace.getWorkspaceAppIcon(MAPS_APP_NAME), 2);
 
         executeOnLauncher(launcher -> {
             assertPagesExist(launcher, 0, 1, 2, 3);
-            assertItemsOnPage(launcher, 0, "Play Store");
+            assertItemsOnPage(launcher, 0, STORE_APP_NAME);
             assertPageEmpty(launcher, 1);
-            assertItemsOnPage(launcher, 2, "Maps");
+            assertItemsOnPage(launcher, 2, MAPS_APP_NAME);
             assertPageEmpty(launcher, 3);
         });
     }
@@ -179,14 +184,14 @@
         Workspace workspace = mLauncher.getWorkspace();
 
         // b/299522368 sometimes the phone app is not present in the hotseat.
-        workspace.dragIcon(workspace.getHotseatAppIcon("Chrome"), 3);
+        workspace.dragIcon(workspace.getHotseatAppIcon(CHROME_APP_NAME), 3);
 
         executeOnLauncher(launcher -> {
             assertPagesExist(launcher, 0, 1, 2, 3);
-            assertItemsOnPage(launcher, 0, "Play Store", "Maps");
+            assertItemsOnPage(launcher, 0, STORE_APP_NAME, MAPS_APP_NAME);
             assertPageEmpty(launcher, 1);
             assertPageEmpty(launcher, 2);
-            assertItemsOnPage(launcher, 3, "Chrome");
+            assertItemsOnPage(launcher, 3, CHROME_APP_NAME);
         });
     }
 
@@ -195,44 +200,44 @@
     public void testMultiplePageDragIcon() {
         Workspace workspace = mLauncher.getWorkspace();
 
-        workspace.dragIcon(workspace.getHotseatAppIcon("Messages"), 2);
+        workspace.dragIcon(workspace.getHotseatAppIcon(MESSAGES_APP_NAME), 2);
 
         workspace.flingBackward();
 
-        workspace.dragIcon(workspace.getWorkspaceAppIcon("Maps"), 5);
+        workspace.dragIcon(workspace.getWorkspaceAppIcon(MAPS_APP_NAME), 5);
 
         executeOnLauncher(launcher -> {
             assertPagesExist(launcher, 0, 1, 2, 3, 4, 5);
-            assertItemsOnPage(launcher, 0, "Play Store");
+            assertItemsOnPage(launcher, 0, STORE_APP_NAME);
             assertPageEmpty(launcher, 1);
-            assertItemsOnPage(launcher, 2, "Messages");
+            assertItemsOnPage(launcher, 2, MESSAGES_APP_NAME);
             assertPageEmpty(launcher, 3);
             assertPageEmpty(launcher, 4);
-            assertItemsOnPage(launcher, 5, "Maps");
+            assertItemsOnPage(launcher, 5, MAPS_APP_NAME);
         });
 
         workspace.flingBackward();
 
-        workspace.dragIcon(workspace.getWorkspaceAppIcon("Messages"), 4);
+        workspace.dragIcon(workspace.getWorkspaceAppIcon(MESSAGES_APP_NAME), 4);
 
         executeOnLauncher(launcher -> {
             assertPagesExist(launcher, 0, 1, 4, 5, 6, 7);
-            assertItemsOnPage(launcher, 0, "Play Store");
+            assertItemsOnPage(launcher, 0, STORE_APP_NAME);
             assertPageEmpty(launcher, 1);
             assertPageEmpty(launcher, 4);
-            assertItemsOnPage(launcher, 5, "Maps");
-            assertItemsOnPage(launcher, 6, "Messages");
+            assertItemsOnPage(launcher, 5, MAPS_APP_NAME);
+            assertItemsOnPage(launcher, 6, MESSAGES_APP_NAME);
             assertPageEmpty(launcher, 7);
         });
 
-        workspace.dragIcon(workspace.getWorkspaceAppIcon("Messages"), -3);
+        workspace.dragIcon(workspace.getWorkspaceAppIcon(MESSAGES_APP_NAME), -3);
 
         executeOnLauncher(launcher -> {
             assertPagesExist(launcher, 0, 1, 4, 5);
-            assertItemsOnPage(launcher, 0, "Play Store");
-            assertItemsOnPage(launcher, 1, "Messages");
+            assertItemsOnPage(launcher, 0, STORE_APP_NAME);
+            assertItemsOnPage(launcher, 1, MESSAGES_APP_NAME);
             assertPageEmpty(launcher, 4);
-            assertItemsOnPage(launcher, 5, "Maps");
+            assertItemsOnPage(launcher, 5, MAPS_APP_NAME);
         });
     }
 
@@ -241,38 +246,38 @@
     public void testEmptyPageDoesNotGetRemovedIfPagePairIsNotEmpty() {
         Workspace workspace = mLauncher.getWorkspace();
 
-        workspace.dragIcon(workspace.getWorkspaceAppIcon("Maps"), 3);
-        workspace.dragIcon(workspace.getHotseatAppIcon("Chrome"), 0);
+        workspace.dragIcon(workspace.getWorkspaceAppIcon(MAPS_APP_NAME), 3);
+        workspace.dragIcon(workspace.getHotseatAppIcon(CHROME_APP_NAME), 0);
 
         executeOnLauncher(launcher -> {
             assertPagesExist(launcher, 0, 1, 2, 3);
-            assertItemsOnPage(launcher, 0, "Play Store");
+            assertItemsOnPage(launcher, 0, STORE_APP_NAME);
             assertPageEmpty(launcher, 1);
-            assertItemsOnPage(launcher, 2, "Chrome");
-            assertItemsOnPage(launcher, 3, "Maps");
+            assertItemsOnPage(launcher, 2, CHROME_APP_NAME);
+            assertItemsOnPage(launcher, 3, MAPS_APP_NAME);
         });
 
-        workspace.dragIcon(workspace.getWorkspaceAppIcon("Maps"), -1);
+        workspace.dragIcon(workspace.getWorkspaceAppIcon(MAPS_APP_NAME), -1);
 
         executeOnLauncher(launcher -> {
             assertPagesExist(launcher, 0, 1, 2, 3);
-            assertItemsOnPage(launcher, 0, "Play Store");
-            assertItemsOnPage(launcher, 1, "Maps");
-            assertItemsOnPage(launcher, 2, "Chrome");
+            assertItemsOnPage(launcher, 0, STORE_APP_NAME);
+            assertItemsOnPage(launcher, 1, MAPS_APP_NAME);
+            assertItemsOnPage(launcher, 2, CHROME_APP_NAME);
             assertPageEmpty(launcher, 3);
         });
 
         // Move Chrome to the right panel as well, to make sure pages are not deleted whichever
         // page is the empty one
         workspace.flingForward();
-        workspace.dragIcon(workspace.getWorkspaceAppIcon("Chrome"), 1);
+        workspace.dragIcon(workspace.getWorkspaceAppIcon(CHROME_APP_NAME), 1);
 
         executeOnLauncher(launcher -> {
             assertPagesExist(launcher, 0, 1, 2, 3);
-            assertItemsOnPage(launcher, 0, "Play Store");
-            assertItemsOnPage(launcher, 1, "Maps");
+            assertItemsOnPage(launcher, 0, STORE_APP_NAME);
+            assertItemsOnPage(launcher, 1, MAPS_APP_NAME);
             assertPageEmpty(launcher, 2);
-            assertItemsOnPage(launcher, 3, "Chrome");
+            assertItemsOnPage(launcher, 3, CHROME_APP_NAME);
         });
     }
 
@@ -281,25 +286,25 @@
     public void testEmptyPagesGetRemovedIfBothPagesAreEmpty() {
         Workspace workspace = mLauncher.getWorkspace();
 
-        workspace.dragIcon(workspace.getWorkspaceAppIcon("Play Store"), 2);
-        workspace.dragIcon(workspace.getHotseatAppIcon("Chrome"), 1);
+        workspace.dragIcon(workspace.getWorkspaceAppIcon(STORE_APP_NAME), 2);
+        workspace.dragIcon(workspace.getHotseatAppIcon(CHROME_APP_NAME), 1);
 
         executeOnLauncher(launcher -> {
             assertPagesExist(launcher, 0, 1, 2, 3);
-            assertItemsOnPage(launcher, 0, "Maps");
+            assertItemsOnPage(launcher, 0, MAPS_APP_NAME);
             assertPageEmpty(launcher, 1);
-            assertItemsOnPage(launcher, 2, "Play Store");
-            assertItemsOnPage(launcher, 3, "Chrome");
+            assertItemsOnPage(launcher, 2, STORE_APP_NAME);
+            assertItemsOnPage(launcher, 3, CHROME_APP_NAME);
         });
 
-        workspace.dragIcon(workspace.getWorkspaceAppIcon("Chrome"), -1);
+        workspace.dragIcon(workspace.getWorkspaceAppIcon(CHROME_APP_NAME), -1);
         workspace.flingForward();
-        workspace.dragIcon(workspace.getWorkspaceAppIcon("Play Store"), -2);
+        workspace.dragIcon(workspace.getWorkspaceAppIcon(STORE_APP_NAME), -2);
 
         executeOnLauncher(launcher -> {
             assertPagesExist(launcher, 0, 1);
-            assertItemsOnPage(launcher, 0, "Play Store", "Maps");
-            assertItemsOnPage(launcher, 1, "Chrome");
+            assertItemsOnPage(launcher, 0, STORE_APP_NAME, MAPS_APP_NAME);
+            assertItemsOnPage(launcher, 1, CHROME_APP_NAME);
         });
     }
 
@@ -308,28 +313,28 @@
     public void testMiddleEmptyPagesGetRemoved() {
         Workspace workspace = mLauncher.getWorkspace();
 
-        workspace.dragIcon(workspace.getWorkspaceAppIcon("Maps"), 2);
-        workspace.dragIcon(workspace.getHotseatAppIcon("Messages"), 3);
+        workspace.dragIcon(workspace.getWorkspaceAppIcon(MAPS_APP_NAME), 2);
+        workspace.dragIcon(workspace.getHotseatAppIcon(MESSAGES_APP_NAME), 3);
 
         executeOnLauncher(launcher -> {
             assertPagesExist(launcher, 0, 1, 2, 3, 4, 5);
-            assertItemsOnPage(launcher, 0, "Play Store");
+            assertItemsOnPage(launcher, 0, STORE_APP_NAME);
             assertPageEmpty(launcher, 1);
-            assertItemsOnPage(launcher, 2, "Maps");
+            assertItemsOnPage(launcher, 2, MAPS_APP_NAME);
             assertPageEmpty(launcher, 3);
             assertPageEmpty(launcher, 4);
-            assertItemsOnPage(launcher, 5, "Messages");
+            assertItemsOnPage(launcher, 5, MESSAGES_APP_NAME);
         });
 
         workspace.flingBackward();
-        workspace.dragIcon(workspace.getWorkspaceAppIcon("Maps"), 2);
+        workspace.dragIcon(workspace.getWorkspaceAppIcon(MAPS_APP_NAME), 2);
 
         executeOnLauncher(launcher -> {
             assertPagesExist(launcher, 0, 1, 4, 5);
-            assertItemsOnPage(launcher, 0, "Play Store");
+            assertItemsOnPage(launcher, 0, STORE_APP_NAME);
             assertPageEmpty(launcher, 1);
-            assertItemsOnPage(launcher, 4, "Maps");
-            assertItemsOnPage(launcher, 5, "Messages");
+            assertItemsOnPage(launcher, 4, MAPS_APP_NAME);
+            assertItemsOnPage(launcher, 5, MESSAGES_APP_NAME);
         });
     }
 
diff --git a/tests/src/com/android/launcher3/util/DisplayControllerTest.kt b/tests/src/com/android/launcher3/util/DisplayControllerTest.kt
index a94dd2e..8670d40 100644
--- a/tests/src/com/android/launcher3/util/DisplayControllerTest.kt
+++ b/tests/src/com/android/launcher3/util/DisplayControllerTest.kt
@@ -42,11 +42,13 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.doNothing
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
-import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.doNothing
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
 import org.mockito.stubbing.Answer
 
 /** Unit tests for {@link DisplayController} */
@@ -56,13 +58,13 @@
 
     private val appContext: Context = ApplicationProvider.getApplicationContext()
 
-    @Mock private lateinit var context: SandboxContext
-    @Mock private lateinit var windowManagerProxy: WindowManagerProxy
-    @Mock private lateinit var launcherPrefs: LauncherPrefs
-    @Mock private lateinit var displayManager: DisplayManager
-    @Mock private lateinit var display: Display
-    @Mock private lateinit var resources: Resources
-    @Mock private lateinit var displayInfoChangeListener: DisplayInfoChangeListener
+    private val context: SandboxContext = mock()
+    private val windowManagerProxy: WindowManagerProxy = mock()
+    private val launcherPrefs: LauncherPrefs = mock()
+    private val displayManager: DisplayManager = mock()
+    private val display: Display = mock()
+    private val resources: Resources = mock()
+    private val displayInfoChangeListener: DisplayInfoChangeListener = mock()
 
     private lateinit var displayController: DisplayController
 
@@ -88,7 +90,6 @@
 
     @Before
     fun setUp() {
-        MockitoAnnotations.initMocks(this)
         whenever(context.getObject(eq(WindowManagerProxy.INSTANCE))).thenReturn(windowManagerProxy)
         whenever(context.getObject(eq(LauncherPrefs.INSTANCE))).thenReturn(launcherPrefs)
         whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(false)
@@ -112,10 +113,10 @@
 
         whenever(windowManagerProxy.getNavigationMode(any())).thenReturn(NavigationMode.NO_BUTTON)
         // Mock context
-        whenever(context.createWindowContext(any(), any(), nullable())).thenReturn(context)
+        whenever(context.createWindowContext(any(), any(), anyOrNull())).thenReturn(context)
         whenever(context.getSystemService(eq(DisplayManager::class.java)))
             .thenReturn(displayManager)
-        doNothing().`when`(context).registerComponentCallbacks(any())
+        doNothing().whenever(context).registerComponentCallbacks(any())
 
         // Mock display
         whenever(display.rotation).thenReturn(displayInfo.rotation)
diff --git a/tests/src/com/android/launcher3/util/KotlinMockitoHelpers.kt b/tests/src/com/android/launcher3/util/KotlinMockitoHelpers.kt
deleted file mode 100644
index c9c9616..0000000
--- a/tests/src/com/android/launcher3/util/KotlinMockitoHelpers.kt
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.util
-
-/**
- * Kotlin versions of popular mockito methods that can return null in situations when Kotlin expects
- * a non-null value. Kotlin will throw an IllegalStateException when this takes place ("x must not
- * be null"). To fix this, we can use methods that modify the return type to be nullable. This
- * causes Kotlin to skip the null checks.
- */
-import org.mockito.ArgumentCaptor
-import org.mockito.Mockito
-
-/**
- * Returns Mockito.eq() as nullable type to avoid java.lang.IllegalStateException when null is
- * returned.
- *
- * Generic T is nullable because implicitly bounded by Any?.
- */
-fun <T> eq(obj: T): T = Mockito.eq<T>(obj)
-
-/**
- * Returns Mockito.same() as nullable type to avoid java.lang.IllegalStateException when null is
- * returned.
- *
- * Generic T is nullable because implicitly bounded by Any?.
- */
-fun <T> same(obj: T): T = Mockito.same<T>(obj)
-
-/**
- * Returns Mockito.any() as nullable type to avoid java.lang.IllegalStateException when null is
- * returned.
- *
- * Generic T is nullable because implicitly bounded by Any?.
- */
-fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
-
-inline fun <reified T> any(): T = any(T::class.java)
-
-/** Kotlin type-inferred version of Mockito.nullable() */
-inline fun <reified T> nullable(): T? = Mockito.nullable(T::class.java)
-
-/**
- * Returns ArgumentCaptor.capture() as nullable type to avoid java.lang.IllegalStateException when
- * null is returned.
- *
- * Generic T is nullable because implicitly bounded by Any?.
- */
-fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
-
-/**
- * Helper function for creating an argumentCaptor in kotlin.
- *
- * Generic T is nullable because implicitly bounded by Any?.
- */
-inline fun <reified T : Any> argumentCaptor(): ArgumentCaptor<T> =
-    ArgumentCaptor.forClass(T::class.java)
-
-/**
- * Helper function for creating new mocks, without the need to pass in a [Class] instance.
- *
- * Generic T is nullable because implicitly bounded by Any?.
- */
-inline fun <reified T : Any> mock(): T = Mockito.mock(T::class.java)
-
-/**
- * A kotlin implemented wrapper of [ArgumentCaptor] which prevents the following exception when
- * kotlin tests are mocking kotlin objects and the methods take non-null parameters:
- * ```
- *     java.lang.NullPointerException: capture() must not be null
- * ```
- */
-class KotlinArgumentCaptor<T> constructor(clazz: Class<T>) {
-    private val wrapped: ArgumentCaptor<T> = ArgumentCaptor.forClass(clazz)
-    fun capture(): T = wrapped.capture()
-    val value: T
-        get() = wrapped.value
-}
-
-/**
- * Helper function for creating an argumentCaptor in kotlin.
- *
- * Generic T is nullable because implicitly bounded by Any?.
- */
-inline fun <reified T : Any> kotlinArgumentCaptor(): KotlinArgumentCaptor<T> =
-    KotlinArgumentCaptor(T::class.java)
-
-/**
- * Helper function for creating and using a single-use ArgumentCaptor in kotlin.
- *
- * ```
- *    val captor = argumentCaptor<Foo>()
- *    verify(...).someMethod(captor.capture())
- *    val captured = captor.value
- * ```
- *
- * becomes:
- * ```
- *    val captured = withArgCaptor<Foo> { verify(...).someMethod(capture()) }
- * ```
- *
- * NOTE: this uses the KotlinArgumentCaptor to avoid the NullPointerException.
- */
-inline fun <reified T : Any> withArgCaptor(block: KotlinArgumentCaptor<T>.() -> Unit): T =
-    kotlinArgumentCaptor<T>().apply { block() }.value
diff --git a/tests/src/com/android/launcher3/util/LockedUserStateTest.kt b/tests/src/com/android/launcher3/util/LockedUserStateTest.kt
index 92ab2cb..2c4a54f 100644
--- a/tests/src/com/android/launcher3/util/LockedUserStateTest.kt
+++ b/tests/src/com/android/launcher3/util/LockedUserStateTest.kt
@@ -26,29 +26,27 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.Mockito.`when`
-import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.whenever
 
 /** Unit tests for {@link LockedUserState} */
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class LockedUserStateTest {
 
-    @Mock lateinit var userManager: UserManager
-    @Mock lateinit var context: Context
+    private val userManager: UserManager = mock()
+    private val context: Context = mock()
 
     @Before
     fun setup() {
-        MockitoAnnotations.initMocks(this)
-        `when`(context.getSystemService(UserManager::class.java)).thenReturn(userManager)
+        whenever(context.getSystemService(UserManager::class.java)).thenReturn(userManager)
     }
 
     @Test
     fun runOnUserUnlocked_runs_action_immediately_if_already_unlocked() {
-        `when`(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(true)
+        whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(true)
         val action: Runnable = mock()
         LockedUserState(context).runOnUserUnlocked(action)
         verify(action).run()
@@ -56,7 +54,7 @@
 
     @Test
     fun runOnUserUnlocked_waits_to_run_action_until_user_is_unlocked() {
-        `when`(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(false)
+        whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(false)
         val action: Runnable = mock()
         val state = LockedUserState(context)
         state.runOnUserUnlocked(action)
@@ -67,13 +65,13 @@
 
     @Test
     fun isUserUnlocked_returns_true_when_user_is_unlocked() {
-        `when`(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(true)
+        whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(true)
         assertThat(LockedUserState(context).isUserUnlocked).isTrue()
     }
 
     @Test
     fun isUserUnlocked_returns_false_when_user_is_locked() {
-        `when`(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(false)
+        whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(false)
         assertThat(LockedUserState(context).isUserUnlocked).isFalse()
     }
 }
diff --git a/tests/src/com/android/launcher3/util/TestConstants.java b/tests/src/com/android/launcher3/util/TestConstants.java
new file mode 100644
index 0000000..6f3c63a
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/TestConstants.java
@@ -0,0 +1,29 @@
+/*
+ * 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.util;
+
+public class TestConstants {
+    public static class AppNames {
+
+        public static final String TEST_APP_NAME = "LauncherTestApp";
+        public static final String DUMMY_APP_NAME = "Aardwolf";
+        public static final String MAPS_APP_NAME = "Maps";
+        public static final String STORE_APP_NAME = "Play Store";
+        public static final String GMAIL_APP_NAME = "Gmail";
+        public static final String CHROME_APP_NAME = "Chrome";
+        public static final String MESSAGES_APP_NAME = "Messages";
+    }
+}
diff --git a/tests/src/com/android/launcher3/util/rule/TISBindRule.java b/tests/src/com/android/launcher3/util/rule/TISBindRule.java
deleted file mode 100644
index 3ec4a29..0000000
--- a/tests/src/com/android/launcher3/util/rule/TISBindRule.java
+++ /dev/null
@@ -1,81 +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.util.rule;
-
-import android.app.UiAutomation;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.IBinder;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-public class TISBindRule implements TestRule {
-    public static String TAG = "TISBindRule";
-    public static String INTENT_FILTER = "android.intent.action.QUICKSTEP_SERVICE";
-    public static String TIS_PERMISSIONS = "android.permission.STATUS_BAR_SERVICE";
-
-    private String getLauncherPackageName(Context context) {
-        return ComponentName.unflattenFromString(context.getString(
-                com.android.internal.R.string.config_recentsComponentName)).getPackageName();
-    }
-
-    private ServiceConnection createConnection() {
-        return new ServiceConnection() {
-            @Override
-            public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
-                Log.d(TAG, "Connected to TouchInteractionService");
-            }
-
-            @Override
-            public void onServiceDisconnected(ComponentName componentName) {
-                Log.d(TAG, "Disconnected from TouchInteractionService");
-            }
-        };
-    }
-
-    @NonNull
-    @Override
-    public Statement apply(@NonNull Statement base, @NonNull Description description) {
-        return new Statement() {
-
-            @Override
-            public void evaluate() throws Throwable {
-                Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
-                final ServiceConnection connection = createConnection();
-                UiAutomation uiAutomation =
-                        InstrumentationRegistry.getInstrumentation().getUiAutomation();
-                uiAutomation.adoptShellPermissionIdentity(TIS_PERMISSIONS);
-                Intent launchIntent = new Intent(INTENT_FILTER);
-                launchIntent.setPackage(getLauncherPackageName(context));
-                context.bindService(launchIntent, connection, Context.BIND_AUTO_CREATE);
-                uiAutomation.dropShellPermissionIdentity();
-                try {
-                    base.evaluate();
-                } finally {
-                    context.unbindService(connection);
-                }
-            }
-        };
-    }
-}
diff --git a/tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt b/tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt
index ccbae4f..e70ea18 100644
--- a/tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt
+++ b/tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt
@@ -132,7 +132,9 @@
         for (i in 0 until viewCaptureData!!.windowDataCount) {
             frameCount += viewCaptureData!!.getWindowData(i).frameDataCount
         }
-        assertTrue("Empty ViewCapture data", frameCount > 0)
+
+        val mayProduceNoFrames = description.getAnnotation(MayProduceNoFrames::class.java) != null
+        assertTrue("Empty ViewCapture data", mayProduceNoFrames || frameCount > 0)
 
         val anomalies: Map<String, String> = ViewCaptureAnalyzer.getAnomalies(viewCaptureData)
         if (!anomalies.isEmpty()) {
@@ -159,4 +161,8 @@
             )
         }
     }
+
+    @Retention(AnnotationRetention.RUNTIME)
+    @Target(AnnotationTarget.FUNCTION)
+    annotation class MayProduceNoFrames
 }
diff --git a/tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java b/tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java
index 4b65439..51b7b18 100644
--- a/tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java
+++ b/tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java
@@ -62,18 +62,6 @@
                     + "NexusOverviewActionsView:id/overview_actions_view|FrameLayout:id"
                     + "/select_mode_buttons|ImageButton:id/close",
             DRAG_LAYER
-                    + "NexusOverviewActionsView:id/overview_actions_view|LinearLayout:id"
-                    + "/action_buttons|Button:id/action_screenshot",
-            DRAG_LAYER
-                    + "NexusOverviewActionsView:id/overview_actions_view|LinearLayout:id"
-                    + "/action_buttons|Button:id/action_select",
-            DRAG_LAYER
-                    + "NexusOverviewActionsView:id/overview_actions_view|LinearLayout:id"
-                    + "/action_buttons|Button:id/action_split",
-            DRAG_LAYER
-                    + "NexusOverviewActionsView:id/overview_actions_view|LinearLayout:id"
-                    + "/action_buttons|Space:id/action_split_space",
-            DRAG_LAYER
                     + "PopupContainerWithArrow:id/popup_container|LinearLayout:id"
                     + "/deep_shortcuts_container|DeepShortcutView:id/deep_shortcut_material"
                     + "|DeepShortcutTextView:id/bubble_text",
@@ -116,23 +104,14 @@
             RECENTS_DRAG_LAYER + "FallbackRecentsView:id/overview_panel",
             DRAG_LAYER
                     + "NexusOverviewActionsView:id/overview_actions_view"
-                    + "|LinearLayout:id/action_buttons|Button:id/action_screenshot",
+                    + "|LinearLayout:id/action_buttons",
             RECENTS_DRAG_LAYER
                     + "NexusOverviewActionsView:id/overview_actions_view"
-                    + "|LinearLayout:id/action_buttons|Button:id/action_screenshot",
+                    + "|LinearLayout:id/action_buttons",
+            DRAG_LAYER + "IconView",
             DRAG_LAYER
-                    + "NexusOverviewActionsView:id/overview_actions_view"
-                    + "|LinearLayout:id/action_buttons|Button:id/action_select",
-            RECENTS_DRAG_LAYER
-                    + "NexusOverviewActionsView:id/overview_actions_view"
-                    + "|LinearLayout:id/action_buttons|Button:id/action_select",
-            DRAG_LAYER
-                    + "NexusOverviewActionsView:id/overview_actions_view"
-                    + "|LinearLayout:id/action_buttons|Button:id/action_split",
-            RECENTS_DRAG_LAYER
-                    + "NexusOverviewActionsView:id/overview_actions_view"
-                    + "|LinearLayout:id/action_buttons|Button:id/action_split",
-            DRAG_LAYER + "IconView"
+                    + "OptionsPopupView:id/popup_container|DeepShortcutView:id/system_shortcut"
+                    + "|BubbleTextView:id/bubble_text"
     ));
 
     // Minimal increase or decrease of view's alpha between frames that triggers the error.
diff --git a/tests/src/com/android/launcher3/util/viewcapture_analysis/FlashDetector.java b/tests/src/com/android/launcher3/util/viewcapture_analysis/FlashDetector.java
index 8b88ace..e333074 100644
--- a/tests/src/com/android/launcher3/util/viewcapture_analysis/FlashDetector.java
+++ b/tests/src/com/android/launcher3/util/viewcapture_analysis/FlashDetector.java
@@ -38,7 +38,8 @@
 
     private static final IgnoreNode IGNORED_NODES_ROOT = buildIgnoreNodesTree(List.of(
             CONTENT + "LauncherRootView:id/launcher|FloatingIconView",
-            DRAG_LAYER + "LauncherRecentsView:id/overview_panel|TaskView|TextView",
+            DRAG_LAYER + "LauncherRecentsView:id/overview_panel|TaskView",
+            DRAG_LAYER + "LauncherRecentsView:id/overview_panel|ClearAllButton:id/clear_all",
             DRAG_LAYER
                     + "LauncherAllAppsContainerView:id/apps_view|AllAppsRecyclerView:id"
                     + "/apps_list_view|BubbleTextView:id/icon",
@@ -53,7 +54,9 @@
                     + "/add_item_bottom_sheet|LinearLayout:id/add_item_bottom_sheet_content"
                     + "|ScrollView:id/widget_preview_scroll_view|WidgetCell:id/widget_cell"
                     + "|WidgetCellPreview:id/widget_preview_container|ImageView:id/widget_badge",
-            RECENTS_DRAG_LAYER + "FallbackRecentsView:id/overview_panel|TaskView|IconView:id/icon",
+            RECENTS_DRAG_LAYER + "FallbackRecentsView:id/overview_panel|TaskView",
+            RECENTS_DRAG_LAYER
+                    + "FallbackRecentsView:id/overview_panel|ClearAllButton:id/clear_all",
             DRAG_LAYER + "SearchContainerView:id/apps_view",
             DRAG_LAYER + "LauncherDragView",
             DRAG_LAYER + "FloatingTaskView|FloatingTaskThumbnailView:id/thumbnail",
@@ -64,7 +67,8 @@
                     + "WidgetsTwoPaneSheet|SpringRelativeLayout:id/container|LinearLayout:id"
                     + "/linear_layout_container|FrameLayout:id/recycler_view_container"
                     + "|FrameLayout:id/widgets_two_pane_sheet_recyclerview|WidgetsRecyclerView:id"
-                    + "/primary_widgets_list_view|WidgetsListHeader:id/widgets_list_header"
+                    + "/primary_widgets_list_view|WidgetsListHeader:id/widgets_list_header",
+            DRAG_LAYER + "NexusOverviewActionsView:id/overview_actions_view"
     ));
 
     // Per-AnalysisNode data that's specific to this detector.
diff --git a/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitch.java b/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitch.java
index 2a98a24..a1d8059 100644
--- a/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitch.java
+++ b/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitch.java
@@ -37,7 +37,6 @@
     private static final Pattern EVENT_ALT_TAB_UP = Pattern.compile(
             "KeyboardQuickSwitchView key event: KeyEvent.*?action=ACTION_UP.*?keyCode=KEYCODE_TAB"
                     + ".*?metaState=META_ALT_ON");
-
     private static final Pattern EVENT_ALT_SHIFT_TAB_DOWN = Pattern.compile(
             "KeyboardQuickSwitchView key event: KeyEvent.*?action=ACTION_DOWN.*?keyCode=KEYCODE_TAB"
                     + ".*?metaState=META_ALT_ON|META_SHIFT_ON");
@@ -50,7 +49,10 @@
     private static final Pattern EVENT_ALT_ESC_UP = Pattern.compile(
             "KeyboardQuickSwitchView key event: KeyEvent.*?action=ACTION_UP"
                     + ".*?keyCode=KEYCODE_ESCAPE.*?metaState=META_ALT_ON");
-    private static final Pattern EVENT_ALT_LEFT_UP = Pattern.compile(
+    private static final Pattern EVENT_KQS_ALT_LEFT_UP = Pattern.compile(
+            "KeyboardQuickSwitchView key event: KeyEvent.*?action=ACTION_UP"
+                    + ".*?keyCode=KEYCODE_ALT_LEFT");
+    private static final Pattern EVENT_HOME_ALT_LEFT_UP = Pattern.compile(
             "Key event: KeyEvent.*?action=ACTION_UP"
                     + ".*?keyCode=KEYCODE_ALT_LEFT");
 
@@ -82,22 +84,21 @@
      */
     public KeyboardQuickSwitch moveFocusForward() {
         try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
-                "want to move keyboard quick switch focus forward")) {
+                "want to move keyboard quick switch focus forward");
+             LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
             mLauncher.waitForLauncherObject(KEYBOARD_QUICK_SWITCH_RES_ID);
 
-            try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
-                mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_TAB_DOWN);
-                mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_TAB_UP);
-                mLauncher.assertTrue("Failed to press alt+tab",
-                        mLauncher.getDevice().pressKeyCode(
-                                KeyEvent.KEYCODE_TAB, KeyEvent.META_ALT_ON));
+            mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_TAB_DOWN);
+            mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_TAB_UP);
+            mLauncher.assertTrue("Failed to press alt+tab",
+                    mLauncher.getDevice().pressKeyCode(
+                            KeyEvent.KEYCODE_TAB, KeyEvent.META_ALT_ON));
 
-                try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer(
-                        "pressed alt+tab")) {
-                    mLauncher.waitForLauncherObject(KEYBOARD_QUICK_SWITCH_RES_ID);
+            try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer(
+                    "pressed alt+tab")) {
+                mLauncher.waitForLauncherObject(KEYBOARD_QUICK_SWITCH_RES_ID);
 
-                    return this;
-                }
+                return this;
             }
         }
     }
@@ -117,23 +118,22 @@
      */
     public KeyboardQuickSwitch moveFocusBackward() {
         try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
-                "want to move keyboard quick switch focus backward")) {
+                "want to move keyboard quick switch focus backward");
+             LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
             mLauncher.waitForLauncherObject(KEYBOARD_QUICK_SWITCH_RES_ID);
 
-            try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
-                mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_SHIFT_TAB_DOWN);
-                mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_SHIFT_TAB_UP);
-                mLauncher.assertTrue("Failed to press alt+shift+tab",
-                        mLauncher.getDevice().pressKeyCode(
-                                KeyEvent.KEYCODE_TAB,
-                                KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON));
+            mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_SHIFT_TAB_DOWN);
+            mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_SHIFT_TAB_UP);
+            mLauncher.assertTrue("Failed to press alt+shift+tab",
+                    mLauncher.getDevice().pressKeyCode(
+                            KeyEvent.KEYCODE_TAB,
+                            KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON));
 
-                try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer(
-                        "pressed alt+shift+tab")) {
-                    mLauncher.waitForLauncherObject(KEYBOARD_QUICK_SWITCH_RES_ID);
+            try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer(
+                    "pressed alt+shift+tab")) {
+                mLauncher.waitForLauncherObject(KEYBOARD_QUICK_SWITCH_RES_ID);
 
-                    return this;
-                }
+                return this;
             }
         }
     }
@@ -146,27 +146,28 @@
      */
     public void dismiss() {
         try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
-                "want to dismiss keyboard quick switch view")) {
+                "want to dismiss keyboard quick switch view");
+             LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
             mLauncher.waitForLauncherObject(KEYBOARD_QUICK_SWITCH_RES_ID);
 
-            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.assertTrue("Failed to press alt+tab",
-                        mLauncher.getDevice().pressKeyCode(
-                                KeyEvent.KEYCODE_ESCAPE, KeyEvent.META_ALT_ON));
+            mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_ESC_DOWN);
+            mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_ESC_UP);
+            mLauncher.assertTrue("Failed to press alt+tab",
+                    mLauncher.getDevice().pressKeyCode(
+                            KeyEvent.KEYCODE_ESCAPE, KeyEvent.META_ALT_ON));
 
-                try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer(
-                        "pressed alt+esc")) {
-                    mLauncher.waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID);
-                    if (mExpectHomeKeyEventsOnDismiss) {
-                        mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_LEFT_UP);
-                    }
-                    mLauncher.unpressKeyCode(KeyEvent.KEYCODE_ALT_LEFT, 0);
+            try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer(
+                    "pressed alt+esc")) {
+                mLauncher.waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID);
 
-                    // Verify the final state is the same as the initial state
-                    mLauncher.verifyContainerType(mStartingContainerType);
+                // Verify the final state is the same as the initial state
+                mLauncher.verifyContainerType(mStartingContainerType);
+
+                // Wait until the device has fully settled before unpressing the key code
+                if (mExpectHomeKeyEventsOnDismiss) {
+                    mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_HOME_ALT_LEFT_UP);
                 }
+                mLauncher.unpressKeyCode(KeyEvent.KEYCODE_ALT_LEFT, 0);
             }
         }
     }
@@ -180,7 +181,9 @@
      * @param expectedPackageName the package name of the expected launched app
      */
     public LaunchedAppState launchFocusedAppTask(@NonNull String expectedPackageName) {
-        return (LaunchedAppState) launchFocusedTask(expectedPackageName);
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+            return (LaunchedAppState) launchFocusedTask(expectedPackageName);
+        }
     }
 
     /**
@@ -190,22 +193,29 @@
      * {@link #launchFocusedAppTask(String)}.
      */
     public Overview launchFocusedOverviewTask() {
-        return (Overview) launchFocusedTask(null);
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+            return (Overview) launchFocusedTask(null);
+        }
     }
 
     private LauncherInstrumentation.VisibleContainer launchFocusedTask(
             @Nullable String expectedPackageName) {
-        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+        try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
                 "want to launch focused task: "
                         + (expectedPackageName == null ? "Overview" : expectedPackageName))) {
+            mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_KQS_ALT_LEFT_UP);
             mLauncher.unpressKeyCode(KeyEvent.KEYCODE_ALT_LEFT, 0);
-            mLauncher.waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID);
 
-            if (expectedPackageName != null) {
-                mLauncher.assertAppLaunched(expectedPackageName);
-                return mLauncher.getLaunchedAppState();
-            } else {
-                return mLauncher.getOverview();
+            try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer(
+                    "un-pressed left alt")) {
+                mLauncher.waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID);
+
+                if (expectedPackageName != null) {
+                    mLauncher.assertAppLaunched(expectedPackageName);
+                    return mLauncher.getLaunchedAppState();
+                } else {
+                    return mLauncher.getOverview();
+                }
             }
         }
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitchSource.java b/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitchSource.java
index b7e3d38..677ed04 100644
--- a/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitchSource.java
+++ b/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitchSource.java
@@ -32,7 +32,8 @@
         LauncherInstrumentation launcher = getLauncher();
 
         try (LauncherInstrumentation.Closable c1 = launcher.addContextLayer(
-                "want to show keyboard quick switch object")) {
+                "want to show keyboard quick switch object");
+             LauncherInstrumentation.Closable e = launcher.eventsCheck()) {
             launcher.pressAndHoldKeyCode(KeyEvent.KEYCODE_TAB, KeyEvent.META_ALT_LEFT_ON);
 
             try (LauncherInstrumentation.Closable c2 = launcher.addContextLayer(
diff --git a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
index 9f8fb92..efeb5f6 100644
--- a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
+++ b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
@@ -54,13 +54,13 @@
     private static final int STASHED_TASKBAR_BOTTOM_EDGE_DP = 1;
 
     private final Condition<UiDevice, Boolean> mStashedTaskbarHintScaleCondition =
-            device -> mLauncher.getTestInfo(REQUEST_STASHED_TASKBAR_SCALE).getFloat(
-                    TestProtocol.TEST_INFO_RESPONSE_FIELD) - UNSTASHED_TASKBAR_HANDLE_HINT_SCALE
+            device -> Math.abs(mLauncher.getTestInfo(REQUEST_STASHED_TASKBAR_SCALE).getFloat(
+                    TestProtocol.TEST_INFO_RESPONSE_FIELD) - UNSTASHED_TASKBAR_HANDLE_HINT_SCALE)
                     < 0.00001f;
 
     private final Condition<UiDevice, Boolean> mStashedTaskbarDefaultScaleCondition =
-            device -> mLauncher.getTestInfo(REQUEST_STASHED_TASKBAR_SCALE).getFloat(
-                    TestProtocol.TEST_INFO_RESPONSE_FIELD) - 1f < 0.00001f;
+            device -> Math.abs(mLauncher.getTestInfo(REQUEST_STASHED_TASKBAR_SCALE).getFloat(
+                    TestProtocol.TEST_INFO_RESPONSE_FIELD) - 1f) < 0.00001f;
 
     LaunchedAppState(LauncherInstrumentation launcher) {
         super(launcher);
@@ -284,7 +284,8 @@
             Point stashedTaskbarHintArea = new Point(mLauncher.getRealDisplaySize().x / 2,
                     mLauncher.getRealDisplaySize().y - 1);
             mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_HOVER_ENTER,
-                    new Point(stashedTaskbarHintArea.x, stashedTaskbarHintArea.y), null);
+                    new Point(stashedTaskbarHintArea.x, stashedTaskbarHintArea.y), null,
+                    InputDevice.SOURCE_MOUSE);
 
             mLauncher.getDevice().wait(mStashedTaskbarHintScaleCondition,
                     LauncherInstrumentation.WAIT_TIME_MS);
@@ -296,7 +297,7 @@
                         mLauncher.getRealDisplaySize().y - 500);
                 mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_HOVER_EXIT,
                         new Point(outsideStashedTaskbarHintArea.x, outsideStashedTaskbarHintArea.y),
-                        null);
+                        null, InputDevice.SOURCE_MOUSE);
 
                 mLauncher.getDevice().wait(mStashedTaskbarDefaultScaleCondition,
                         LauncherInstrumentation.WAIT_TIME_MS);
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index d7f9c78..307f192 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -27,6 +27,7 @@
 import static com.android.launcher3.tapl.Folder.FOLDER_CONTENT_RES_ID;
 import static com.android.launcher3.tapl.TestHelpers.getOverviewPackageName;
 import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
+import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_NUM_ALL_APPS_COLUMNS;
 
 import android.app.ActivityManager;
 import android.app.Instrumentation;
@@ -353,6 +354,11 @@
                 .getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD);
     }
 
+    public int getNumAllAppsColumns() {
+        return getTestInfo(REQUEST_NUM_ALL_APPS_COLUMNS).getInt(
+                TestProtocol.TEST_INFO_RESPONSE_FIELD);
+    }
+
     public boolean isTablet() {
         return getTestInfo(TestProtocol.REQUEST_IS_TABLET)
                 .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD);
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index fc589bd..2a2a83f 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -28,7 +28,6 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.SystemClock;
-import android.util.Log;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 
@@ -302,10 +301,6 @@
         final UiObject2 workspace = verifyActiveContainer();
         List<UiObject2> workspaceIcons =
                 mLauncher.waitForObjectsInContainer(workspace, AppIcon.getAnyAppIconSelector());
-        Log.d("b/288944469", "List size = " + workspaceIcons.size());
-        for (int i = 0; i < workspaceIcons.size(); i++) {
-            Log.d("b/288944469", "index = " + i + " tesxt = " + workspaceIcons.get(i).getText());
-        }
         return workspaceIcons.stream()
                 .collect(
                         Collectors.toMap(