Merge "Introduce haptic when all apps state transition happens."
diff --git a/Android.bp b/Android.bp
index 43d28c9..c8d9186 100644
--- a/Android.bp
+++ b/Android.bp
@@ -33,6 +33,9 @@
 
 android_library {
     name: "launcher-aosp-tapl",
+    libs: [
+        "framework-statsd",
+    ],
     static_libs: [
         "androidx.annotation_annotation",
         "androidx.test.runner",
@@ -192,6 +195,9 @@
     resource_dirs: [
         "quickstep/res",
     ],
+    libs: [
+        "framework-statsd",
+    ],
     static_libs: [
         "Launcher3ResLib",
         "SystemUISharedLib",
@@ -261,6 +267,9 @@
     resource_dirs: [
         "quickstep/res",
     ],
+    libs: [
+        "framework-statsd",
+    ],
     static_libs: [
         "SystemUI-statsd",
         "SystemUISharedLib",
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index d67b23b..31c0f5f 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -30,6 +30,7 @@
          determines how many thumbnails will be fetched in the background. -->
     <integer name="recentsThumbnailCacheSize">3</integer>
     <integer name="recentsIconCacheSize">12</integer>
+    <integer name="recentsScrollHapticMinGapMillis">20</integer>
 
     <!-- Assistant Gesture -->
     <integer name="assistant_gesture_min_time_threshold">200</integer>
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 2534699..1f4be5b 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -90,6 +90,8 @@
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
 import com.android.systemui.unfold.config.UnfoldTransitionConfig;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.List;
 import java.util.stream.Stream;
 
@@ -619,4 +621,12 @@
             recentsView.finishRecentsAnimation(/* toRecents= */ true, null);
         }
     }
+
+    @Override
+    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+        super.dump(prefix, fd, writer, args);
+        if (mDepthController != null) {
+            mDepthController.dump(prefix, writer);
+        }
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index 5769f0b..55a140d 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -25,7 +25,9 @@
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
 import static com.android.launcher3.Utilities.getDevicePrefs;
 import static com.android.launcher3.hybridhotseat.HotseatPredictionModel.convertDataModelToAppTargetBundle;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
+import android.app.StatsManager;
 import android.app.prediction.AppPredictionContext;
 import android.app.prediction.AppPredictionManager;
 import android.app.prediction.AppPredictor;
@@ -39,12 +41,14 @@
 import android.content.pm.ShortcutInfo;
 import android.os.UserHandle;
 import android.util.Log;
+import android.util.StatsEvent;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
 
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.InstanceIdSequence;
 import com.android.launcher3.model.BgDataModel.FixedContainerItems;
@@ -53,10 +57,10 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.shortcuts.ShortcutKey;
-import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.IntSparseArrayMap;
 import com.android.launcher3.util.PersistedItemArray;
 import com.android.quickstep.logging.StatsLogCompatManager;
+import com.android.systemui.shared.system.SysUiStatsLog;
 
 import java.util.Collections;
 import java.util.List;
@@ -85,6 +89,7 @@
 
     private final InvariantDeviceProfile mIDP;
     private final AppEventProducer mAppEventProducer;
+    private final StatsManager mStatsManager;
 
     protected boolean mActive = false;
 
@@ -93,6 +98,7 @@
 
         mIDP = InvariantDeviceProfile.INSTANCE.get(context);
         StatsLogCompatManager.LOGS_CONSUMER.add(mAppEventProducer);
+        mStatsManager = context.getSystemService(StatsManager.class);
     }
 
     @Override
@@ -155,10 +161,60 @@
             additionalSnapshotEvents(instanceId);
             prefs.edit().putLong(LAST_SNAPSHOT_TIME_MILLIS, now).apply();
         }
+
+        // Only register for launcher snapshot logging if this is the primary ModelDelegate
+        // instance, as there will be additional instances that may be destroyed at any time.
+        if (mIsPrimaryInstance) {
+            registerSnapshotLoggingCallback();
+        }
     }
 
     protected void additionalSnapshotEvents(InstanceId snapshotInstanceId){}
 
+    /**
+     * Registers a callback to log launcher workspace layout using Statsd pulled atom.
+     */
+    protected void registerSnapshotLoggingCallback() {
+        if (mStatsManager == null) {
+            Log.d(TAG, "Failed to get StatsManager");
+        }
+
+        try {
+            mStatsManager.setPullAtomCallback(
+                    SysUiStatsLog.LAUNCHER_LAYOUT_SNAPSHOT,
+                    null /* PullAtomMetadata */,
+                    MODEL_EXECUTOR,
+                    (i, eventList) -> {
+                        InstanceId instanceId = new InstanceIdSequence().newInstanceId();
+                        IntSparseArrayMap<ItemInfo> itemsIdMap;
+                        synchronized (mDataModel) {
+                            itemsIdMap = mDataModel.itemsIdMap.clone();
+                        }
+
+                        for (ItemInfo info : itemsIdMap) {
+                            FolderInfo parent = info.container > 0
+                                    ? (FolderInfo) itemsIdMap.get(info.container) : null;
+                            LauncherAtom.ItemInfo itemInfo = info.buildProto(parent);
+                            Log.d(TAG, itemInfo.toString());
+                            StatsEvent statsEvent = StatsLogCompatManager.buildStatsEvent(itemInfo,
+                                    instanceId);
+                            eventList.add(statsEvent);
+                        }
+                        Log.d(TAG,
+                                String.format(
+                                        "Successfully logged %d workspace items with instanceId=%d",
+                                        itemsIdMap.size(), instanceId.getId()));
+                        additionalSnapshotEvents(instanceId);
+                        return StatsManager.PULL_SUCCESS;
+                    }
+            );
+            Log.d(TAG, "Successfully registered for launcher snapshot logging!");
+        } catch (RuntimeException e) {
+            Log.e(TAG, "Failed to register launcher snapshot logging callback with StatsManager",
+                    e);
+        }
+    }
+
     @Override
     public void validateData() {
         super.validateData();
@@ -175,7 +231,9 @@
         super.destroy();
         mActive = false;
         StatsLogCompatManager.LOGS_CONSUMER.remove(mAppEventProducer);
-
+        if (mIsPrimaryInstance) {
+            mStatsManager.clearPullAtomCallback(SysUiStatsLog.LAUNCHER_LAYOUT_SNAPSHOT);
+        }
         destroyPredictors();
     }
 
@@ -221,7 +279,7 @@
     private void registerPredictor(PredictorState state, AppPredictor predictor) {
         state.predictor = predictor;
         state.predictor.registerPredictionUpdates(
-                Executors.MODEL_EXECUTOR, t -> handleUpdate(state, t));
+                MODEL_EXECUTOR, t -> handleUpdate(state, t));
         state.predictor.requestPredictionUpdate();
     }
 
@@ -236,7 +294,7 @@
     private void registerWidgetsPredictor(AppPredictor predictor) {
         mWidgetsRecommendationState.predictor = predictor;
         mWidgetsRecommendationState.predictor.registerPredictionUpdates(
-                Executors.MODEL_EXECUTOR, targets -> {
+                MODEL_EXECUTOR, targets -> {
                     if (mWidgetsRecommendationState.setTargets(targets)) {
                         // No diff, skip
                         return;
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
index 3e2fb63..9d70cfa 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
@@ -44,6 +44,8 @@
 import com.android.systemui.shared.system.BlurUtils;
 import com.android.systemui.shared.system.WallpaperManagerCompat;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.function.Consumer;
 
 /**
@@ -138,6 +140,11 @@
      */
     private float mDepth;
     /**
+     * Last blur value, in pixels, that was applied.
+     * For debugging purposes.
+     */
+    private int mCurrentBlur;
+    /**
      * If we're launching and app and should not be blurring the screen for performance reasons.
      */
     private boolean mBlurDisabledForAppLaunch;
@@ -306,10 +313,10 @@
         if (supportsBlur) {
             boolean opaque = mLauncher.getScrimView().isFullyOpaque();
 
-            int blur = !mCrossWindowBlursEnabled || mBlurDisabledForAppLaunch
+            mCurrentBlur = !mCrossWindowBlursEnabled || mBlurDisabledForAppLaunch
                     ? 0 : (int) (depth * mMaxBlurRadius);
             SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()
-                    .setBackgroundBlurRadius(mSurface, blur)
+                    .setBackgroundBlurRadius(mSurface, mCurrentBlur)
                     .setOpaque(mSurface, opaque);
 
             // Set early wake-up flags when we know we're executing an expensive operation, this way
@@ -348,4 +355,18 @@
         mwAnimation.setAutoCancel(true);
         mwAnimation.start();
     }
+
+    public void dump(String prefix, PrintWriter writer) {
+        writer.println(prefix + this.getClass().getSimpleName());
+        writer.println(prefix + "\tmMaxBlurRadius=" + mMaxBlurRadius);
+        writer.println(prefix + "\tmCrossWindowBlursEnabled=" + mCrossWindowBlursEnabled);
+        writer.println(prefix + "\tmSurface=" + mSurface);
+        writer.println(prefix + "\tmOverlayScrollProgress=" + mOverlayScrollProgress);
+        writer.println(prefix + "\tmDepth=" + mDepth);
+        writer.println(prefix + "\tmCurrentBlur=" + mCurrentBlur);
+        writer.println(prefix + "\tmBlurDisabledForAppLaunch=" + mBlurDisabledForAppLaunch);
+        writer.println(prefix + "\tmInEarlyWakeUp=" + mInEarlyWakeUp);
+        writer.println(prefix + "\tmIgnoreStateChangesDuringMultiWindowAnimation="
+                + mIgnoreStateChangesDuringMultiWindowAnimation);
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java
index 43f015c..978bd47 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java
@@ -77,6 +77,7 @@
         @Override
         public void onTransitionFinished() {
             mMoveFromCenterAnimator.onTransitionFinished();
+            mMoveFromCenterAnimator.clearRegisteredViews();
         }
 
         @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginInitializerImpl.java b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginInitializerImpl.java
deleted file mode 100644
index 0e12e30..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginInitializerImpl.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.launcher3.uioverrides.plugins;
-
-import android.content.Context;
-
-import com.android.launcher3.Utilities;
-import com.android.systemui.shared.plugins.PluginInitializer;
-
-public class PluginInitializerImpl implements PluginInitializer {
-    @Override
-    public String[] getPrivilegedPlugins(Context context) {
-        return new String[0];
-    }
-
-    @Override
-    public void handleWtfs() {
-    }
-
-    public boolean isDebuggable() {
-        return Utilities.IS_DEBUG_DEVICE;
-    }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
index e12f42e..df0ac7c 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
@@ -24,17 +24,19 @@
 import android.content.Intent;
 import android.content.pm.ResolveInfo;
 
+import com.android.launcher3.Utilities;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.systemui.plugins.Plugin;
 import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginInstanceManager;
+import com.android.systemui.shared.plugins.PluginActionManager;
+import com.android.systemui.shared.plugins.PluginInstance;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.shared.plugins.PluginManagerImpl;
 import com.android.systemui.shared.plugins.PluginPrefs;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
@@ -52,29 +54,36 @@
 
     private PluginManagerWrapper(Context c) {
         mContext = c;
-        PluginInitializerImpl pluginInitializer  = new PluginInitializerImpl();
         mPluginEnabler = new PluginEnablerImpl(c);
-        PluginInstanceManager.Factory instanceManagerFactory = new PluginInstanceManager.Factory(
-                c, c.getPackageManager(), c.getMainExecutor(), MODEL_EXECUTOR, pluginInitializer,
+        List<String> privilegedPlugins = Collections.emptyList();
+        PluginInstance.Factory instanceFactory = new PluginInstance.Factory(
+                getClass().getClassLoader(), new PluginInstance.InstanceFactory<>(),
+                new PluginInstance.VersionChecker(), privilegedPlugins,
+                Utilities.IS_DEBUG_DEVICE);
+        PluginActionManager.Factory instanceManagerFactory = new PluginActionManager.Factory(
+                c, c.getPackageManager(), c.getMainExecutor(), MODEL_EXECUTOR,
                 c.getSystemService(NotificationManager.class), mPluginEnabler,
-                Arrays.asList(pluginInitializer.getPrivilegedPlugins(c)));
+                privilegedPlugins, instanceFactory);
 
         mPluginManager = new PluginManagerImpl(c, instanceManagerFactory,
-                pluginInitializer.isDebuggable(),
+                Utilities.IS_DEBUG_DEVICE,
                 Optional.ofNullable(Thread.getDefaultUncaughtExceptionHandler()), mPluginEnabler,
-                new PluginPrefs(c), Arrays.asList(pluginInitializer.getPrivilegedPlugins(c)));
+                new PluginPrefs(c), privilegedPlugins);
     }
 
     public PluginEnablerImpl getPluginEnabler() {
         return mPluginEnabler;
     }
 
-    public void addPluginListener(PluginListener<? extends Plugin> listener, Class<?> pluginClass) {
+    /** */
+    public <T extends Plugin> void addPluginListener(
+            PluginListener<T> listener, Class<T> pluginClass) {
         addPluginListener(listener, pluginClass, false);
     }
 
-    public void addPluginListener(PluginListener<? extends Plugin> listener, Class<?> pluginClass,
-            boolean allowMultiple) {
+    /** */
+    public <T extends Plugin> void addPluginListener(
+            PluginListener<T> listener, Class<T> pluginClass, boolean allowMultiple) {
         mPluginManager.addPluginListener(listener, pluginClass, allowMultiple);
     }
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index 283743d..ef6f53e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -24,7 +24,7 @@
 import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
 import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
-import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
+import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 
 import android.animation.ObjectAnimator;
@@ -38,11 +38,11 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.states.StateAnimationConfig;
-import com.android.launcher3.util.VibratorWrapper;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.util.MotionPauseDetector;
 import com.android.quickstep.util.OverviewToHomeAnim;
+import com.android.quickstep.util.VibratorWrapper;
 import com.android.quickstep.views.RecentsView;
 
 /**
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 40c3e02..ff3c517 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -41,7 +41,7 @@
 import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_RIGHT;
 import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_UP;
 import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
-import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
+import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC;
 import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
 import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
 import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
@@ -67,14 +67,15 @@
 import com.android.launcher3.touch.BaseSwipeDetector;
 import com.android.launcher3.touch.BothAxesSwipeDetector;
 import com.android.launcher3.util.TouchController;
-import com.android.launcher3.util.VibratorWrapper;
 import com.android.quickstep.AnimatedFloat;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.util.MotionPauseDetector;
+import com.android.quickstep.util.VibratorWrapper;
 import com.android.quickstep.util.WorkspaceRevealAnim;
 import com.android.quickstep.views.LauncherRecentsView;
+import com.android.quickstep.views.RecentsView;
 
 /**
  * Handles quick switching to a recent task from the home screen. To give as much flexibility to
@@ -398,6 +399,14 @@
             nonOverviewAnim.setFloatValues(startProgress, endProgress);
             mNonOverviewAnim.dispatchOnStart();
         }
+        if (targetState == QUICK_SWITCH) {
+            // Navigating to quick switch, add scroll feedback since the first time is not
+            // considered a scroll by the RecentsView.
+            VibratorWrapper.INSTANCE.get(mLauncher).vibrate(
+                    RecentsView.SCROLL_VIBRATION_PRIMITIVE,
+                    RecentsView.SCROLL_VIBRATION_PRIMITIVE_SCALE,
+                    RecentsView.SCROLL_VIBRATION_FALLBACK);
+        }
 
         nonOverviewAnim.setDuration(Math.max(xDuration, yDuration));
         mNonOverviewAnim.setEndAction(() -> onAnimationToStateCompleted(targetState));
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index 0603ba5..010f463 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -22,6 +22,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.os.SystemClock;
+import android.os.VibrationEffect;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.animation.Interpolator;
@@ -41,6 +42,7 @@
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.util.VibratorWrapper;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 
@@ -55,6 +57,12 @@
     private static final long MIN_TASK_DISMISS_ANIMATION_DURATION = 300;
     private static final long MAX_TASK_DISMISS_ANIMATION_DURATION = 600;
 
+    public static final int TASK_DISMISS_VIBRATION_PRIMITIVE =
+            Utilities.ATLEAST_R ? VibrationEffect.Composition.PRIMITIVE_TICK : -1;
+    public static final float TASK_DISMISS_VIBRATION_PRIMITIVE_SCALE = 1f;
+    public static final VibrationEffect TASK_DISMISS_VIBRATION_FALLBACK =
+            VibratorWrapper.EFFECT_TEXTURE_TICK;
+
     protected final T mActivity;
     private final SingleAxisSwipeDetector mDetector;
     private final RecentsView mRecentsView;
@@ -334,10 +342,10 @@
             fling = false;
         }
         PagedOrientationHandler orientationHandler = mRecentsView.getPagedOrientationHandler();
+        boolean goingUp = orientationHandler.isGoingUp(velocity, mIsRtl);
         float progress = mCurrentAnimation.getProgressFraction();
         float interpolatedProgress = mCurrentAnimation.getInterpolatedProgress();
         if (fling) {
-            boolean goingUp = orientationHandler.isGoingUp(velocity, mIsRtl);
             goingToEnd = goingUp == mCurrentAnimationIsGoingUp;
         } else {
             goingToEnd = interpolatedProgress > SUCCESS_TRANSITION_PROGRESS;
@@ -357,6 +365,10 @@
         mCurrentAnimation.startWithVelocity(mActivity, goingToEnd,
                 velocity * orientationHandler.getSecondaryTranslationDirectionFactor(),
                 mEndDisplacement, animationDuration);
+        if (goingUp && goingToEnd) {
+            VibratorWrapper.INSTANCE.get(mActivity).vibrate(TASK_DISMISS_VIBRATION_PRIMITIVE,
+                    TASK_DISMISS_VIBRATION_PRIMITIVE_SCALE, TASK_DISMISS_VIBRATION_FALLBACK);
+        }
     }
 
     private void clearState() {
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 830737d..1e841da 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -36,7 +36,6 @@
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK;
-import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
 import static com.android.quickstep.GestureState.GestureEndTarget.HOME;
 import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
 import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK;
@@ -46,6 +45,7 @@
 import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_CANCELED;
 import static com.android.quickstep.GestureState.STATE_RECENTS_SCROLLING_FINISHED;
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
+import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC;
 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
@@ -92,7 +92,6 @@
 import com.android.launcher3.tracing.InputConsumerProto;
 import com.android.launcher3.tracing.SwipeHandlerProto;
 import com.android.launcher3.util.TraceHelper;
-import com.android.launcher3.util.VibratorWrapper;
 import com.android.launcher3.util.WindowBounds;
 import com.android.quickstep.BaseActivityInterface.AnimationFactory;
 import com.android.quickstep.GestureState.GestureEndTarget;
@@ -112,6 +111,7 @@
 import com.android.quickstep.util.SwipePipToHomeAnimator;
 import com.android.quickstep.util.TaskViewSimulator;
 import com.android.quickstep.util.TransformParams;
+import com.android.quickstep.util.VibratorWrapper;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -945,6 +945,7 @@
                 mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT);
                 // Notify swipe-to-home (recents animation) is finished
                 SystemUiProxy.INSTANCE.get(mContext).notifySwipeToHomeFinished();
+                LauncherSplitScreenListener.INSTANCE.getNoCreate().notifySwipingToHome();
                 break;
             case RECENTS:
                 mStateCallback.setState(STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
@@ -1805,8 +1806,13 @@
                 mGestureState.updateLastStartedTaskId(taskId);
                 boolean hasTaskPreviouslyAppeared = mGestureState.getPreviouslyAppearedTaskIds()
                         .contains(taskId);
+                boolean isOldTaskSplit = LauncherSplitScreenListener.INSTANCE.getNoCreate()
+                        .getRunningSplitTaskIds().length > 0;
                 nextTask.launchTask(success -> {
                     resultCallback.accept(success);
+                    if (isOldTaskSplit) {
+                        SystemUiProxy.INSTANCE.getNoCreate().exitSplitScreen(taskId);
+                    }
                     if (success) {
                         if (hasTaskPreviouslyAppeared) {
                             onRestartPreviouslyAppearedTask();
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 8fb851c..b232464 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -32,6 +32,7 @@
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.util.RunnableList;
 import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
+import com.android.quickstep.util.LauncherSplitScreenListener;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -157,6 +158,7 @@
             }
             if (cmd.type == TYPE_HOME) {
                 mService.startActivity(mOverviewComponentObserver.getHomeIntent());
+                LauncherSplitScreenListener.INSTANCE.getNoCreate().notifySwipingToHome();
                 return true;
             }
         } else {
@@ -175,6 +177,7 @@
                     return launchTask(recents, getNextTask(recents), cmd);
                 case TYPE_HOME:
                     recents.startHome();
+                    LauncherSplitScreenListener.INSTANCE.getNoCreate().notifySwipingToHome();
                     return true;
             }
         }
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index 51a491e..ebe46fe 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -89,13 +89,13 @@
                 LauncherSplitScreenListener.INSTANCE.getNoCreate()
                         .getRunningSplitTaskIds().length > 1;
 
-        TaskViewSimulator primaryTVS = new TaskViewSimulator(context,
-                gestureState.getActivityInterface());
-        primaryTVS.getOrientationState().update(
-                mDeviceState.getRotationTouchHelper().getCurrentActiveRotation(),
-                mDeviceState.getRotationTouchHelper().getDisplayRotation());
         mTargetGluer = new RemoteTargetGluer(mContext, mGestureState.getActivityInterface());
         mRemoteTargetHandles = mTargetGluer.getRemoteTargetHandles();
+        runActionOnRemoteHandles(remoteTargetHandle ->
+                remoteTargetHandle.getTaskViewSimulator().getOrientationState().update(
+                        mDeviceState.getRotationTouchHelper().getCurrentActiveRotation(),
+                        mDeviceState.getRotationTouchHelper().getDisplayRotation()
+                ));
     }
 
     protected void initTransitionEndpoints(DeviceProfile dp) {
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 7d2d413..aea2d4c 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -533,10 +533,17 @@
         }
     }
 
-    public void exitSplitScreen() {
+    /**
+     * To be called whenever the user exits out of split screen apps (either by launching another
+     * app or by swiping home)
+     * @param topTaskId The taskId of the new app that was launched. System will then move this task
+     *                  to the front of what the user sees while removing all other split stages.
+     *                  If swiping to home (or there is no task to put at the top), can pass in -1.
+     */
+    public void exitSplitScreen(int topTaskId) {
         if (mSplitScreen != null) {
             try {
-                mSplitScreen.exitSplitScreen();
+                mSplitScreen.exitSplitScreen(topTaskId);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call exitSplitScreen");
             }
diff --git a/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java b/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java
index 7465db3..b2b2f59 100644
--- a/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java
+++ b/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java
@@ -43,7 +43,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.util.VibratorWrapper;
+import com.android.quickstep.util.VibratorWrapper;
 
 /** Forked from platform/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarEdgePanel.java. */
 public class EdgeBackGesturePanel extends View {
diff --git a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
index fc1dd6f..851cccf 100644
--- a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
+++ b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
@@ -16,7 +16,6 @@
 package com.android.quickstep.interaction;
 
 import static com.android.launcher3.Utilities.squaredHypot;
-import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
 import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.ASSISTANT_COMPLETED;
 import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.ASSISTANT_NOT_STARTED_BAD_ANGLE;
 import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.ASSISTANT_NOT_STARTED_SWIPE_TOO_SHORT;
@@ -26,6 +25,7 @@
 import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION;
 import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.OVERVIEW_GESTURE_COMPLETED;
 import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE;
+import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC;
 
 import android.animation.ValueAnimator;
 import android.content.Context;
@@ -47,11 +47,11 @@
 import com.android.launcher3.R;
 import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.util.VibratorWrapper;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.util.MotionPauseDetector;
 import com.android.quickstep.util.NavBarPosition;
 import com.android.quickstep.util.TriggerSwipeUpTouchTracker;
+import com.android.quickstep.util.VibratorWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
 
 /** Utility class to handle Home and Assistant gestures. */
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index 6575996..7893f8d 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -31,9 +31,11 @@
 
 import android.content.Context;
 import android.util.Log;
+import android.util.StatsEvent;
 import android.view.View;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
 import androidx.slice.SliceItem;
 
@@ -135,6 +137,36 @@
     }
 
     /**
+     * Builds {@link StatsEvent} from {@link LauncherAtom.ItemInfo}. Used for pulled atom callback
+     * implementation.
+     */
+    public static StatsEvent buildStatsEvent(LauncherAtom.ItemInfo info,
+            @Nullable InstanceId instanceId) {
+        return SysUiStatsLog.buildStatsEvent(
+                SysUiStatsLog.LAUNCHER_LAYOUT_SNAPSHOT, // atom ID,
+                LAUNCHER_WORKSPACE_SNAPSHOT.getId(), // event_id = 1;
+                info.getAttribute().getNumber() * ATTRIBUTE_MULTIPLIER
+                        + info.getItemCase().getNumber(), // item_id = 2;
+                instanceId == null ? 0 : instanceId.getId(), //instance_id = 3;
+                0, //uid = 4 [(is_uid) = true];
+                getPackageName(info), // package_name = 5;
+                getComponentName(info), // component_name = 6;
+                getGridX(info, false), //grid_x = 7 [default = -1];
+                getGridY(info, false), //grid_y = 8 [default = -1];
+                getPageId(info), // page_id = 9 [default = -2];
+                getGridX(info, true), //grid_x_parent = 10 [default = -1];
+                getGridY(info, true), //grid_y_parent = 11 [default = -1];
+                getParentPageId(info), //page_id_parent = 12 [default = -2];
+                getHierarchy(info), // container_id = 13;
+                info.getIsWork(), // is_work_profile = 14;
+                info.getAttribute().getNumber(), // attribute_id = 15;
+                getCardinality(info), // cardinality = 16;
+                info.getWidget().getSpanX(), // span_x = 17 [default = 1];
+                info.getWidget().getSpanY() // span_y = 18 [default = 1];
+        );
+    }
+
+    /**
      * Helps to construct and write statsd compatible log message.
      */
     private static class StatsCompatLogger implements StatsLogger {
diff --git a/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java b/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java
index 0f4ed01..fa4cddc 100644
--- a/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java
+++ b/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java
@@ -44,8 +44,6 @@
             if (frozen) {
                 mPersistentGroupedIds = getRunningSplitTaskIds();
             } else {
-                // TODO(b/198310766) Need to also explicitly exit split screen if
-                //  we're not currently viewing split screened apps
                 mPersistentGroupedIds = EMPTY_ARRAY;
             }
         }
@@ -53,8 +51,11 @@
 
     /**
      * Gets set to current split taskIDs whenever the task list is frozen, and set to empty array
-     * whenever task list unfreezes.
-     * When not null, this indicates that we need to load a GroupedTaskView as the most recent
+     * whenever task list unfreezes. This also gets set to empty array whenever the user swipes to
+     * home - in that case the task list does not unfreeze immediately after the gesture, so it's
+     * done via {@link #notifySwipingToHome()}.
+     *
+     * When not empty, this indicates that we need to load a GroupedTaskView as the most recent
      * page, so user can quickswitch back to a grouped task.
      */
     private int[] mPersistentGroupedIds;
@@ -140,6 +141,18 @@
         }
     }
 
+    /** Notifies SystemUi to remove any split screen state */
+    public void notifySwipingToHome() {
+        boolean hasSplitTasks = LauncherSplitScreenListener.INSTANCE.getNoCreate()
+                .getPersistentSplitIds().length > 0;
+        if (!hasSplitTasks) {
+            return;
+        }
+
+        SystemUiProxy.INSTANCE.getNoCreate().exitSplitScreen(-1);
+        mPersistentGroupedIds = EMPTY_ARRAY;
+    }
+
     private void resetTaskId(StagedSplitTaskPosition taskPosition) {
         taskPosition.taskId = -1;
     }
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index a089e73..849a7bc 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -296,9 +296,8 @@
         }
 
         float fullScreenProgress = Utilities.boundToRange(this.fullScreenProgress.value, 0, 1);
-        mCurrentFullscreenParams.setProgress(
-                fullScreenProgress, recentsViewScale.value, /*taskViewScale=*/1f, mTaskRect.width(),
-                mDp, mPositionHelper);
+        mCurrentFullscreenParams.setProgress(fullScreenProgress, recentsViewScale.value,
+                /* taskViewScale= */1f, mTaskRect.width(), mDp, mPositionHelper);
 
         // Apply thumbnail matrix
         RectF insets = mCurrentFullscreenParams.mCurrentDrawnInsets;
diff --git a/src/com/android/launcher3/util/VibratorWrapper.java b/quickstep/src/com/android/quickstep/util/VibratorWrapper.java
similarity index 65%
rename from src/com/android/launcher3/util/VibratorWrapper.java
rename to quickstep/src/com/android/quickstep/util/VibratorWrapper.java
index b0defd4..211bd08 100644
--- a/src/com/android/launcher3/util/VibratorWrapper.java
+++ b/quickstep/src/com/android/quickstep/util/VibratorWrapper.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.launcher3.util;
+package com.android.quickstep.util;
 
 import static android.os.VibrationEffect.createPredefined;
 import static android.provider.Settings.System.HAPTIC_FEEDBACK_ENABLED;
@@ -21,15 +21,20 @@
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 
+import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.ContentObserver;
+import android.media.AudioAttributes;
 import android.os.Build;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.provider.Settings;
 
+import com.android.launcher3.Utilities;
+import com.android.launcher3.util.MainThreadInitializedObject;
+
 /**
  * Wrapper around {@link Vibrator} to easily perform haptic feedback where necessary.
  */
@@ -39,8 +44,15 @@
     public static final MainThreadInitializedObject<VibratorWrapper> INSTANCE =
             new MainThreadInitializedObject<>(VibratorWrapper::new);
 
+    public static final AudioAttributes VIBRATION_ATTRS = new AudioAttributes.Builder()
+            .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
+            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+            .build();
+
     public static final VibrationEffect EFFECT_CLICK =
             createPredefined(VibrationEffect.EFFECT_CLICK);
+    public static final VibrationEffect EFFECT_TEXTURE_TICK =
+            VibrationEffect.createPredefined(VibrationEffect.EFFECT_TEXTURE_TICK);
 
     /**
      * Haptic when entering overview.
@@ -78,7 +90,27 @@
     /** Vibrates with the given effect if haptic feedback is available and enabled. */
     public void vibrate(VibrationEffect vibrationEffect) {
         if (mHasVibrator && mIsHapticFeedbackEnabled) {
-            UI_HELPER_EXECUTOR.execute(() -> mVibrator.vibrate(vibrationEffect));
+            UI_HELPER_EXECUTOR.execute(() -> mVibrator.vibrate(vibrationEffect, VIBRATION_ATTRS));
+        }
+    }
+
+    /**
+     * Vibrates with a single primitive, if supported, or use a fallback effect instead. This only
+     * vibrates if haptic feedback is available and enabled.
+     */
+    @SuppressLint("NewApi")
+    public void vibrate(int primitiveId, float primitiveScale, VibrationEffect fallbackEffect) {
+        if (mHasVibrator && mIsHapticFeedbackEnabled) {
+            UI_HELPER_EXECUTOR.execute(() -> {
+                if (Utilities.ATLEAST_R && primitiveId >= 0
+                        && mVibrator.areAllPrimitivesSupported(primitiveId)) {
+                    mVibrator.vibrate(VibrationEffect.startComposition()
+                            .addPrimitive(primitiveId, primitiveScale)
+                            .compose(), VIBRATION_ATTRS);
+                } else {
+                    mVibrator.vibrate(fallbackEffect, VIBRATION_ATTRS);
+                }
+            });
         }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 24d6044..dd470e8 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -35,6 +35,7 @@
 import static com.android.launcher3.anim.Interpolators.ACCEL_0_75;
 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.clampToProgress;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
@@ -78,7 +79,9 @@
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.SystemClock;
 import android.os.UserHandle;
+import android.os.VibrationEffect;
 import android.text.Layout;
 import android.text.StaticLayout;
 import android.text.TextPaint;
@@ -159,6 +162,7 @@
 import com.android.quickstep.util.SurfaceTransactionApplier;
 import com.android.quickstep.util.TaskViewSimulator;
 import com.android.quickstep.util.TransformParams;
+import com.android.quickstep.util.VibratorWrapper;
 import com.android.systemui.plugins.ResourceProvider;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.Task.TaskKey;
@@ -243,6 +247,12 @@
                 }
             };
 
+    public static final int SCROLL_VIBRATION_PRIMITIVE =
+            Utilities.ATLEAST_S ? VibrationEffect.Composition.PRIMITIVE_LOW_TICK : -1;
+    public static final float SCROLL_VIBRATION_PRIMITIVE_SCALE = 0.6f;
+    public static final VibrationEffect SCROLL_VIBRATION_FALLBACK =
+            VibratorWrapper.EFFECT_TEXTURE_TICK;
+
     /**
      * Can be used to tint the color of the RecentsView to simulate a scrim that can views
      * excluded from. Really should be a proper scrim.
@@ -396,6 +406,7 @@
 
     protected final ACTIVITY_TYPE mActivity;
     private final float mFastFlingVelocity;
+    private final int mScrollHapticMinGapMillis;
     private final RecentsModel mModel;
     private final int mGridSideMargin;
     private final ClearAllButton mClearAllButton;
@@ -442,6 +453,7 @@
     private ObjectAnimator mTintingAnimator;
 
     private int mOverScrollShift = 0;
+    private long mScrollLastHapticTimestamp;
 
     /**
      * TODO: Call reloadIdNeeded in onTaskStackChanged.
@@ -628,6 +640,8 @@
         final int rotation = mActivity.getDisplay().getRotation();
         mOrientationState.setRecentsRotation(rotation);
 
+        mScrollHapticMinGapMillis = getResources()
+                .getInteger(R.integer.recentsScrollHapticMinGapMillis);
         mFastFlingVelocity = getResources()
                 .getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
         mModel = RecentsModel.INSTANCE.get(context);
@@ -1228,6 +1242,25 @@
     }
 
     @Override
+    protected void onEdgeAbsorbingScroll() {
+        vibrateForScroll();
+    }
+
+    @Override
+    protected void onScrollOverPageChanged() {
+        vibrateForScroll();
+    }
+
+    private void vibrateForScroll() {
+        long now = SystemClock.uptimeMillis();
+        if (now - mScrollLastHapticTimestamp > mScrollHapticMinGapMillis) {
+            mScrollLastHapticTimestamp = now;
+            VibratorWrapper.INSTANCE.get(mContext).vibrate(SCROLL_VIBRATION_PRIMITIVE,
+                    SCROLL_VIBRATION_PRIMITIVE_SCALE, SCROLL_VIBRATION_FALLBACK);
+        }
+    }
+
+    @Override
     protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) {
         // Enables swiping to the left or right only if the task overlay is not modal.
         if (!isModal()) {
@@ -2001,9 +2034,6 @@
     /**
      * Called only when a swipe-up gesture from an app has completed. Only called after
      * {@link #onGestureAnimationStart} and {@link #onGestureAnimationEnd()}.
-     *
-     * TODO(b/198310766) Need to also explicitly exit split screen if
-     *  the swipe up was to home
      */
     public void onSwipeUpAnimationSuccess() {
         animateUpTaskIconScale();
@@ -2592,10 +2622,13 @@
             runActionOnRemoteHandles(remoteTargetHandle -> {
                 TransformParams params = remoteTargetHandle.getTransformParams();
                 anim.setFloat(params, TransformParams.TARGET_ALPHA, 0,
-                        clampToProgress(ACCEL, 0, 0.5f));
+                        clampToProgress(FINAL_FRAME, 0, 0.5f));
             });
         }
-        anim.setFloat(taskView, VIEW_ALPHA, 0, clampToProgress(ACCEL, 0, 0.5f));
+        boolean isTaskInBottomGridRow = showAsGrid() && !mTopRowIdSet.contains(
+                taskView.getTaskViewId()) && taskView.getTaskViewId() != mFocusedTaskViewId;
+        anim.setFloat(taskView, VIEW_ALPHA, 0,
+                clampToProgress(isTaskInBottomGridRow ? ACCEL : FINAL_FRAME, 0, 0.5f));
         FloatProperty<TaskView> secondaryViewTranslate =
                 taskView.getSecondaryDissmissTranslationProperty();
         int secondaryTaskDimension = mOrientationHandler.getSecondaryDimension(taskView);
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 08b614a..7c558c2 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -99,6 +99,7 @@
 import com.android.quickstep.TaskUtils;
 import com.android.quickstep.TaskViewUtils;
 import com.android.quickstep.util.CancellableTask;
+import com.android.quickstep.util.LauncherSplitScreenListener;
 import com.android.quickstep.util.RecentsOrientedState;
 import com.android.quickstep.util.TaskCornerRadius;
 import com.android.quickstep.util.TransformParams;
@@ -693,8 +694,13 @@
             TestLogging.recordEvent(
                     TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", mTask);
             ActivityOptionsWrapper opts =  mActivity.getActivityLaunchOptions(this, null);
+            boolean isOldTaskSplit = LauncherSplitScreenListener.INSTANCE.getNoCreate()
+                    .getPersistentSplitIds().length > 0;
             if (ActivityManagerWrapper.getInstance()
                     .startActivityFromRecents(mTask.key, opts.options)) {
+                if (isOldTaskSplit) {
+                    SystemUiProxy.INSTANCE.getNoCreate().exitSplitScreen(mTask.key.id);
+                }
                 RecentsView recentsView = getRecentsView();
                 if (ENABLE_QUICKSTEP_LIVE_TILE.get() && recentsView.getRunningTaskViewId() != -1) {
                     recentsView.onTaskLaunchedInLiveTileMode();
@@ -1071,7 +1077,6 @@
 
     private void setNonGridScale(float nonGridScale) {
         mNonGridScale = nonGridScale;
-        updateCornerRadius();
         applyScale();
     }
 
@@ -1102,6 +1107,7 @@
         scale *= mDismissScale;
         setScaleX(scale);
         setScaleY(scale);
+        updateSnapshotRadius();
     }
 
     /**
@@ -1417,29 +1423,25 @@
         mIconView.setVisibility(progress < 1 ? VISIBLE : INVISIBLE);
         mSnapshotView.getTaskOverlay().setFullscreenProgress(progress);
 
-        updateCornerRadius();
+        updateSnapshotRadius();
 
-        mSnapshotView.setFullscreenParams(mCurrentFullscreenParams);
         mOutlineProvider.updateParams(
                 mCurrentFullscreenParams,
                 mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx);
         invalidateOutline();
     }
 
-    private void updateCornerRadius() {
+    private void updateSnapshotRadius() {
         updateCurrentFullscreenParams(mSnapshotView.getPreviewPositionHelper());
+        mSnapshotView.setFullscreenParams(mCurrentFullscreenParams);
     }
 
     void updateCurrentFullscreenParams(PreviewPositionHelper previewPositionHelper) {
         if (getRecentsView() == null) {
             return;
         }
-        mCurrentFullscreenParams.setProgress(
-                mFullscreenProgress,
-                getRecentsView().getScaleX(),
-                mNonGridScale,
-                getWidth(), mActivity.getDeviceProfile(),
-                previewPositionHelper);
+        mCurrentFullscreenParams.setProgress(mFullscreenProgress, getRecentsView().getScaleX(),
+                getScaleX(), getWidth(), mActivity.getDeviceProfile(), previewPositionHelper);
     }
 
     /**
diff --git a/res/xml/default_workspace_splitdisplay_5x5.xml b/res/xml/default_workspace_splitdisplay_5x5.xml
deleted file mode 100644
index 162367b..0000000
--- a/res/xml/default_workspace_splitdisplay_5x5.xml
+++ /dev/null
@@ -1,107 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 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.
--->
-
-<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
-
-    <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
-    <!-- Dialer, Messaging, [Maps/Music], Browser, Camera -->
-    <resolve
-        launcher:container="-101"
-        launcher:screen="0"
-        launcher:x="0"
-        launcher:y="0" >
-        <favorite launcher:uri="#Intent;action=android.intent.action.DIAL;end" />
-        <favorite launcher:uri="tel:123" />
-        <favorite launcher:uri="#Intent;action=android.intent.action.CALL_BUTTON;end" />
-    </resolve>
-
-    <resolve
-        launcher:container="-101"
-        launcher:screen="1"
-        launcher:x="1"
-        launcher:y="0" >
-        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MESSAGING;end" />
-        <favorite launcher:uri="sms:" />
-        <favorite launcher:uri="smsto:" />
-        <favorite launcher:uri="mms:" />
-        <favorite launcher:uri="mmsto:" />
-    </resolve>
-
-    <resolve
-        launcher:container="-101"
-        launcher:screen="2"
-        launcher:x="2"
-        launcher:y="0" >
-        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MAPS;end" />
-        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MUSIC;end" />
-    </resolve>
-
-    <resolve
-        launcher:container="-101"
-        launcher:screen="3"
-        launcher:x="3"
-        launcher:y="0" >
-        <favorite
-            launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_BROWSER;end" />
-        <favorite launcher:uri="http://www.example.com/" />
-    </resolve>
-
-    <resolve
-        launcher:container="-101"
-        launcher:screen="4"
-        launcher:x="4"
-        launcher:y="0" >
-        <favorite launcher:uri="#Intent;action=android.media.action.STILL_IMAGE_CAMERA;end" />
-        <favorite launcher:uri="#Intent;action=android.intent.action.CAMERA_BUTTON;end" />
-    </resolve>
-
-    <!-- Bottom row -->
-    <resolve
-        launcher:screen="0"
-        launcher:x="0"
-        launcher:y="-1" >
-	    <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_EMAIL;end" />
-	    <favorite launcher:uri="mailto:" />
-
-    </resolve>
-
-    <resolve
-        launcher:screen="0"
-        launcher:x="1"
-        launcher:y="-1" >
-	    <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_GALLERY;end" />
-	    <favorite launcher:uri="#Intent;type=images/*;end" />
-
-    </resolve>
-
-    <resolve
-        launcher:screen="0"
-        launcher:x="4"
-        launcher:y="-1" >
-        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MARKET;end" />
-        <favorite launcher:uri="market://details?id=com.android.launcher" />
-    </resolve>
-
-    <!-- Placeholder before we add page pairing in b/196376162 -->
-    <resolve
-        launcher:screen="1"
-        launcher:x="0"
-        launcher:y="-4" >
-        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_GALLERY;end" />
-        <favorite launcher:uri="#Intent;type=images/*;end" />
-    </resolve>
-
-</favorites>
diff --git a/res/xml/device_profiles.xml b/res/xml/device_profiles.xml
index d8ee6f2..256999c 100644
--- a/res/xml/device_profiles.xml
+++ b/res/xml/device_profiles.xml
@@ -105,8 +105,7 @@
         launcher:numFolderColumns="4"
         launcher:numHotseatIcons="5"
         launcher:dbFile="launcher.db"
-        launcher:defaultLayoutId="@xml/default_workspace_5x5"
-        launcher:defaultSplitDisplayLayoutId="@xml/default_workspace_splitdisplay_5x5" >
+        launcher:defaultLayoutId="@xml/default_workspace_5x5" >
 
         <display-option
             launcher:name="Large Phone"
diff --git a/robolectric_tests/res/values/overlayable_icons_test.xml b/robolectric_tests/res/values/overlayable_icons_test.xml
deleted file mode 100644
index 5144e52..0000000
--- a/robolectric_tests/res/values/overlayable_icons_test.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<!--
-   Copyright (C) 2019 The Android Open Source Project
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-        http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
--->
-<resources>
-    <!-- overlayable_icons references all of the drawables in this package
-         that are being overlayed by resource overlays. If you remove/rename
-         any of these resources, you must also change the resource overlay icons.-->
-    <array name="overlayable_icons">
-        <item>@drawable/ic_corp</item>
-        <item>@drawable/ic_drag_handle</item>
-        <item>@drawable/ic_hourglass_top</item>
-        <item>@drawable/ic_info_no_shadow</item>
-        <item>@drawable/ic_install_no_shadow</item>
-        <item>@drawable/ic_palette</item>
-        <item>@drawable/ic_pin</item>
-        <item>@drawable/ic_remove_no_shadow</item>
-        <item>@drawable/ic_setting</item>
-        <item>@drawable/ic_smartspace_preferences</item>
-        <item>@drawable/ic_split_screen</item>
-        <item>@drawable/ic_uninstall_no_shadow</item>
-        <item>@drawable/ic_warning</item>
-        <item>@drawable/ic_widget</item>
-    </array>
-</resources>
diff --git a/robolectric_tests/resources/robolectric.properties b/robolectric_tests/resources/robolectric.properties
deleted file mode 100644
index abb6968..0000000
--- a/robolectric_tests/resources/robolectric.properties
+++ /dev/null
@@ -1,15 +0,0 @@
-sdk=30
-
-shadows= \
-    com.android.launcher3.shadows.LShadowAppPredictionManager \
-    com.android.launcher3.shadows.LShadowAppWidgetManager \
-    com.android.launcher3.shadows.LShadowBackupManager \
-    com.android.launcher3.shadows.LShadowDisplay \
-    com.android.launcher3.shadows.LShadowLauncherApps \
-    com.android.launcher3.shadows.ShadowDeviceFlag \
-    com.android.launcher3.shadows.ShadowLooperExecutor \
-    com.android.launcher3.shadows.ShadowMainThreadInitializedObject \
-    com.android.launcher3.shadows.ShadowOverrides \
-    com.android.launcher3.shadows.ShadowSurfaceTransactionApplier \
-
-application=com.android.launcher3.util.LauncherTestApplication
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowAppPredictionManager.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowAppPredictionManager.java
deleted file mode 100644
index ae051f7..0000000
--- a/robolectric_tests/src/com/android/launcher3/shadows/LShadowAppPredictionManager.java
+++ /dev/null
@@ -1,38 +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.shadows;
-
-import static org.mockito.Mockito.mock;
-
-import android.app.prediction.AppPredictionContext;
-import android.app.prediction.AppPredictionManager;
-import android.app.prediction.AppPredictor;
-
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-
-/**
- * Shadow for {@link AppPredictionManager} which create mock predictors
- */
-@Implements(value = AppPredictionManager.class)
-public class LShadowAppPredictionManager {
-
-    @Implementation
-    public AppPredictor createAppPredictionSession(AppPredictionContext predictionContext) {
-        return mock(AppPredictor.class);
-    }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowAppWidgetManager.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowAppWidgetManager.java
deleted file mode 100644
index 696ffd0..0000000
--- a/robolectric_tests/src/com/android/launcher3/shadows/LShadowAppWidgetManager.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.shadows;
-
-import android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetProviderInfo;
-import android.os.Process;
-import android.os.UserHandle;
-
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.shadows.ShadowAppWidgetManager;
-
-import java.util.List;
-import java.util.stream.Collectors;
-
-/**
- * Extension of {@link ShadowAppWidgetManager} with missing shadow methods
- */
-@Implements(value = AppWidgetManager.class)
-public class LShadowAppWidgetManager extends ShadowAppWidgetManager {
-
-    @Override
-    protected List<AppWidgetProviderInfo> getInstalledProviders() {
-        return getInstalledProvidersForProfile(null);
-    }
-
-    @Implementation
-    public List<AppWidgetProviderInfo> getInstalledProvidersForProfile(UserHandle profile) {
-        UserHandle user = profile == null ? Process.myUserHandle() : profile;
-        return super.getInstalledProviders().stream().filter(
-                info -> user.equals(info.getProfile())).collect(Collectors.toList());
-    }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowBackupManager.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowBackupManager.java
deleted file mode 100644
index eae0101..0000000
--- a/robolectric_tests/src/com/android/launcher3/shadows/LShadowBackupManager.java
+++ /dev/null
@@ -1,45 +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.shadows;
-
-import android.app.backup.BackupManager;
-import android.os.UserHandle;
-import android.util.LongSparseArray;
-
-import androidx.annotation.Nullable;
-
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.shadows.ShadowBackupManager;
-
-/**
- * Extension of {@link ShadowBackupManager} with missing shadow methods
- */
-@Implements(value = BackupManager.class)
-public class LShadowBackupManager extends ShadowBackupManager {
-
-    private LongSparseArray<UserHandle> mProfileMapping = new LongSparseArray<>();
-
-    public void addProfile(long userSerial, UserHandle userHandle) {
-        mProfileMapping.put(userSerial, userHandle);
-    }
-
-    @Implementation
-    @Nullable
-    public UserHandle getUserForAncestralSerialNumber(long ancestralSerialNumber) {
-        return mProfileMapping.get(ancestralSerialNumber);
-    }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowDisplay.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowDisplay.java
deleted file mode 100644
index 3813fa1..0000000
--- a/robolectric_tests/src/com/android/launcher3/shadows/LShadowDisplay.java
+++ /dev/null
@@ -1,54 +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.shadows;
-
-import static org.robolectric.shadow.api.Shadow.directlyOn;
-
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.view.Display;
-
-import org.robolectric.annotation.Implements;
-import org.robolectric.annotation.RealObject;
-import org.robolectric.shadows.ShadowDisplay;
-
-/**
- * Extension of {@link ShadowDisplay} with missing shadow methods
- */
-@Implements(value = Display.class)
-public class LShadowDisplay extends ShadowDisplay {
-
-    private final Rect mInsets = new Rect();
-
-    @RealObject Display realObject;
-
-    /**
-     * Sets the insets for the display
-     */
-    public void setInsets(Rect insets) {
-        mInsets.set(insets);
-    }
-
-    @Override
-    protected void getCurrentSizeRange(Point outSmallestSize, Point outLargestSize) {
-        directlyOn(realObject, Display.class).getCurrentSizeRange(outSmallestSize, outLargestSize);
-        outSmallestSize.x -= mInsets.left + mInsets.right;
-        outLargestSize.x -= mInsets.left + mInsets.right;
-
-        outSmallestSize.y -= mInsets.top + mInsets.bottom;
-        outLargestSize.y -= mInsets.top + mInsets.bottom;
-    }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java
deleted file mode 100644
index 6a6f0fb..0000000
--- a/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.shadows;
-
-import static org.robolectric.util.ReflectionHelpers.ClassParameter;
-import static org.robolectric.util.ReflectionHelpers.callConstructor;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.LauncherActivityInfo;
-import android.content.pm.LauncherApps;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ShortcutInfo;
-import android.os.UserHandle;
-import android.util.ArraySet;
-
-import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.PackageUserKey;
-
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.shadows.ShadowLauncherApps;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.Executor;
-import java.util.stream.Collectors;
-
-/**
- * Extension of {@link ShadowLauncherApps} with missing shadow methods
- */
-@Implements(value = LauncherApps.class)
-public class LShadowLauncherApps extends ShadowLauncherApps {
-
-    public final ArraySet<PackageUserKey> disabledApps = new ArraySet<>();
-    public final ArraySet<ComponentKey> disabledActivities = new ArraySet<>();
-
-    @Implementation
-    @Override
-    protected List<ShortcutInfo> getShortcuts(LauncherApps.ShortcutQuery query, UserHandle user) {
-        try {
-            return super.getShortcuts(query, user);
-        } catch (UnsupportedOperationException e) {
-            return Collections.emptyList();
-        }
-    }
-
-    @Implementation
-    protected boolean isPackageEnabled(String packageName, UserHandle user) {
-        return !disabledApps.contains(new PackageUserKey(packageName, user));
-    }
-
-    @Implementation
-    protected boolean isActivityEnabled(ComponentName component, UserHandle user) {
-        return !disabledActivities.contains(new ComponentKey(component, user));
-    }
-
-    @Implementation
-    protected LauncherActivityInfo resolveActivity(Intent intent, UserHandle user) {
-        ResolveInfo ri = RuntimeEnvironment.application.getPackageManager()
-                .resolveActivity(intent, 0);
-        return ri == null ? null : getLauncherActivityInfo(ri.activityInfo, user);
-    }
-
-    public LauncherActivityInfo getLauncherActivityInfo(
-            ActivityInfo activityInfo, UserHandle user) {
-        return callConstructor(LauncherActivityInfo.class,
-                ClassParameter.from(Context.class, RuntimeEnvironment.application),
-                ClassParameter.from(ActivityInfo.class, activityInfo),
-                ClassParameter.from(UserHandle.class, user));
-    }
-
-    @Implementation
-    public ApplicationInfo getApplicationInfo(String packageName, int flags, UserHandle user)
-            throws PackageManager.NameNotFoundException {
-        return RuntimeEnvironment.application.getPackageManager()
-                .getApplicationInfo(packageName, flags);
-    }
-
-    @Implementation
-    public List<LauncherActivityInfo> getActivityList(String packageName, UserHandle user) {
-        Intent intent = new Intent(Intent.ACTION_MAIN)
-                .addCategory(Intent.CATEGORY_LAUNCHER)
-                .setPackage(packageName);
-        return RuntimeEnvironment.application.getPackageManager().queryIntentActivities(intent, 0)
-                .stream()
-                .map(ri -> getLauncherActivityInfo(ri.activityInfo, user))
-                .collect(Collectors.toList());
-    }
-
-    @Implementation
-    public boolean hasShortcutHostPermission() {
-        return true;
-    }
-
-    @Implementation
-    public List<PackageInstaller.SessionInfo> getAllPackageInstallerSessions() {
-        return RuntimeEnvironment.application.getPackageManager().getPackageInstaller()
-                .getAllSessions();
-    }
-
-    @Implementation
-    public void registerPackageInstallerSessionCallback(
-            Executor executor, PackageInstaller.SessionCallback callback) {
-    }
-
-    @Override
-    protected List<LauncherActivityInfo> getShortcutConfigActivityList(String packageName,
-            UserHandle user) {
-        Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT).setPackage(packageName);
-        return RuntimeEnvironment.application.getPackageManager().queryIntentActivities(intent, 0)
-                .stream()
-                .map(ri -> getLauncherActivityInfo(ri.activityInfo, user))
-                .collect(Collectors.toList());
-    }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/ShadowDeviceFlag.java b/robolectric_tests/src/com/android/launcher3/shadows/ShadowDeviceFlag.java
deleted file mode 100644
index b58e4b7..0000000
--- a/robolectric_tests/src/com/android/launcher3/shadows/ShadowDeviceFlag.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.shadows;
-
-import android.content.Context;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.uioverrides.DeviceFlag;
-import com.android.launcher3.util.LooperExecutor;
-
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.annotation.RealObject;
-import org.robolectric.shadow.api.Shadow;
-
-/**
- * Shadow for {@link LooperExecutor} to provide reset functionality for static executors.
- */
-@Implements(value = DeviceFlag.class, isInAndroidSdk = false)
-public class ShadowDeviceFlag {
-
-    @RealObject private DeviceFlag mRealObject;
-    @Nullable private Boolean mValue;
-
-    /**
-     * Mock change listener as it uses internal system classes not available to robolectric
-     */
-    @Implementation
-    protected void addChangeListener(Context context, Runnable r) { }
-
-    @Implementation
-    protected static boolean getDeviceValue(String key, boolean defaultValue) {
-        return defaultValue;
-    }
-
-    @Implementation
-    public boolean get() {
-        if (mValue != null) {
-            return mValue;
-        }
-        return Shadow.directlyOn(mRealObject, DeviceFlag.class, "get");
-    }
-
-    public void setValue(boolean value) {
-        mValue = new Boolean(value);
-    }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/ShadowLooperExecutor.java b/robolectric_tests/src/com/android/launcher3/shadows/ShadowLooperExecutor.java
deleted file mode 100644
index 57eda7e..0000000
--- a/robolectric_tests/src/com/android/launcher3/shadows/ShadowLooperExecutor.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.shadows;
-
-import static com.android.launcher3.util.Executors.createAndStartNewLooper;
-
-import static org.robolectric.shadow.api.Shadow.directlyOn;
-import static org.robolectric.util.ReflectionHelpers.setField;
-
-import android.os.Handler;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.util.LooperExecutor;
-
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.annotation.RealObject;
-
-/**
- * Shadow for {@link LooperExecutor} to provide reset functionality for static executors.
- */
-@Implements(value = LooperExecutor.class, isInAndroidSdk = false)
-public class ShadowLooperExecutor {
-
-    @RealObject private LooperExecutor mRealExecutor;
-
-    private Handler mOverriddenHandler;
-
-    @Implementation
-    protected Handler getHandler() {
-        if (mOverriddenHandler != null) {
-            return mOverriddenHandler;
-        }
-        Handler handler = directlyOn(mRealExecutor, LooperExecutor.class, "getHandler");
-        Thread thread = handler.getLooper().getThread();
-        if (!thread.isAlive()) {
-            // Robolectric destroys all loopers at the end of every test. Since Launcher maintains
-            // some static threads, they need to be reinitialized in case they were destroyed.
-            setField(mRealExecutor, "mHandler",
-                    new Handler(createAndStartNewLooper(thread.getName())));
-        }
-        return directlyOn(mRealExecutor, LooperExecutor.class, "getHandler");
-    }
-
-    public void setHandler(@Nullable Handler handler) {
-        mOverriddenHandler = handler;
-    }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/ShadowMainThreadInitializedObject.java b/robolectric_tests/src/com/android/launcher3/shadows/ShadowMainThreadInitializedObject.java
deleted file mode 100644
index 6e2ccf8..0000000
--- a/robolectric_tests/src/com/android/launcher3/shadows/ShadowMainThreadInitializedObject.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.shadows;
-
-import static org.robolectric.shadow.api.Shadow.invokeConstructor;
-import static org.robolectric.util.ReflectionHelpers.ClassParameter.from;
-
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.launcher3.util.MainThreadInitializedObject.ObjectProvider;
-
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.annotation.RealObject;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Set;
-import java.util.WeakHashMap;
-
-/**
- * Shadow for {@link MainThreadInitializedObject} to provide reset functionality for static sObjects
- */
-@Implements(value = MainThreadInitializedObject.class, isInAndroidSdk = false)
-public class ShadowMainThreadInitializedObject {
-
-    // Keep reference to all created MainThreadInitializedObject so they can be cleared after test
-    private static Set<MainThreadInitializedObject> sObjects =
-            Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap<>()));
-
-    @RealObject private MainThreadInitializedObject mRealObject;
-
-    @Implementation
-    protected void __constructor__(ObjectProvider provider) {
-        invokeConstructor(MainThreadInitializedObject.class, mRealObject,
-                from(ObjectProvider.class, provider));
-        sObjects.add(mRealObject);
-    }
-
-    /**
-     * Resets all the initialized sObjects to be null
-     */
-    public static void resetInitializedObjects() {
-        for (MainThreadInitializedObject object : new ArrayList<>(sObjects)) {
-            object.initializeForTesting(null);
-        }
-    }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/ShadowOverrides.java b/robolectric_tests/src/com/android/launcher3/shadows/ShadowOverrides.java
deleted file mode 100644
index 131f691..0000000
--- a/robolectric_tests/src/com/android/launcher3/shadows/ShadowOverrides.java
+++ /dev/null
@@ -1,61 +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.shadows;
-
-import android.content.Context;
-
-import com.android.launcher3.util.MainThreadInitializedObject.ObjectProvider;
-import com.android.launcher3.util.ResourceBasedOverride;
-import com.android.launcher3.util.ResourceBasedOverride.Overrides;
-
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.shadow.api.Shadow;
-import org.robolectric.util.ReflectionHelpers.ClassParameter;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Shadow for {@link Overrides} to provide custom overrides for test
- */
-@Implements(value = Overrides.class, isInAndroidSdk = false)
-public class ShadowOverrides {
-
-    private static Map<Class, ObjectProvider> sProviderMap = new HashMap<>();
-
-    @Implementation
-    public static <T extends ResourceBasedOverride> T getObject(
-            Class<T> clazz, Context context, int resId) {
-        ObjectProvider<T> provider = sProviderMap.get(clazz);
-        if (provider != null) {
-            return provider.get(context);
-        }
-        return Shadow.directlyOn(Overrides.class, "getObject",
-                ClassParameter.from(Class.class, clazz),
-                ClassParameter.from(Context.class, context),
-                ClassParameter.from(int.class, resId));
-    }
-
-    public static <T> void setProvider(Class<T> clazz, ObjectProvider<T> provider) {
-        sProviderMap.put(clazz, provider);
-    }
-
-    public static void clearProvider() {
-        sProviderMap.clear();
-    }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/ShadowSurfaceTransactionApplier.java b/robolectric_tests/src/com/android/launcher3/shadows/ShadowSurfaceTransactionApplier.java
deleted file mode 100644
index a9f2f27..0000000
--- a/robolectric_tests/src/com/android/launcher3/shadows/ShadowSurfaceTransactionApplier.java
+++ /dev/null
@@ -1,42 +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.shadows;
-
-import static org.robolectric.shadow.api.Shadow.invokeConstructor;
-import static org.robolectric.util.ReflectionHelpers.ClassParameter.from;
-
-import android.view.View;
-
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.annotation.RealObject;
-
-/**
- * Shadow for SurfaceTransactionApplier to override default functionality
- */
-@Implements(className = "com.android.quickstep.util.SurfaceTransactionApplier",
-        isInAndroidSdk = false)
-public class ShadowSurfaceTransactionApplier {
-
-    @RealObject
-    private Object mRealObject;
-
-    @Implementation
-    protected void __constructor__(View view) {
-        invokeConstructor(mRealObject.getClass(), mRealObject, from(View.class, null));
-    }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/util/LauncherTestApplication.java b/robolectric_tests/src/com/android/launcher3/util/LauncherTestApplication.java
deleted file mode 100644
index efac150..0000000
--- a/robolectric_tests/src/com/android/launcher3/util/LauncherTestApplication.java
+++ /dev/null
@@ -1,50 +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 static org.mockito.Mockito.mock;
-
-import android.app.Application;
-
-import com.android.launcher3.shadows.ShadowMainThreadInitializedObject;
-import com.android.launcher3.shadows.ShadowOverrides;
-import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
-
-import org.robolectric.TestLifecycleApplication;
-import org.robolectric.shadows.ShadowLog;
-
-import java.lang.reflect.Method;
-
-public class LauncherTestApplication extends Application implements TestLifecycleApplication {
-
-    @Override
-    public void beforeTest(Method method) {
-        ShadowLog.stream = System.out;
-
-        // Disable plugins
-        PluginManagerWrapper.INSTANCE.initializeForTesting(mock(PluginManagerWrapper.class));
-    }
-
-    @Override
-    public void prepareTest(Object test) { }
-
-    @Override
-    public void afterTest(Method method) {
-        ShadowLog.stream = null;
-        ShadowMainThreadInitializedObject.resetInitializedObjects();
-        ShadowOverrides.clearProvider();
-    }
-}
diff --git a/robolectric_tests/unstaged/SettingsCacheTest.java b/robolectric_tests/unstaged/SettingsCacheTest.java
deleted file mode 100644
index fbf4c63..0000000
--- a/robolectric_tests/unstaged/SettingsCacheTest.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.util;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.content.Context;
-import android.net.Uri;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-
-import java.util.Collections;
-
-@RunWith(RobolectricTestRunner.class)
-public class SettingsCacheTest {
-
-    public static final Uri KEY_SYSTEM_URI_TEST1 = Uri.parse("content://settings/system/test1");
-    public static final Uri KEY_SYSTEM_URI_TEST2 = Uri.parse("content://settings/system/test2");;
-
-    private SettingsCache.OnChangeListener mChangeListener;
-    private SettingsCache mSettingsCache;
-
-    @Before
-    public void setup() {
-        mChangeListener = mock(SettingsCache.OnChangeListener.class);
-        Context targetContext = RuntimeEnvironment.application;
-        mSettingsCache = SettingsCache.INSTANCE.get(targetContext);
-        mSettingsCache.register(KEY_SYSTEM_URI_TEST1, mChangeListener);
-    }
-
-    @Test
-    public void listenerCalledOnChange() {
-        mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST1);
-        verify(mChangeListener, times(1)).onSettingsChanged(true);
-    }
-
-    @Test
-    public void getValueRespectsDefaultValue() {
-        // Case of key not found
-        boolean val = mSettingsCache.getValue(KEY_SYSTEM_URI_TEST1, 0);
-        assertFalse(val);
-    }
-
-    @Test
-    public void getValueHitsCache() {
-        mSettingsCache.setKeyCache(Collections.singletonMap(KEY_SYSTEM_URI_TEST1, true));
-        boolean val = mSettingsCache.getValue(KEY_SYSTEM_URI_TEST1, 0);
-        assertTrue(val);
-    }
-
-    @Test
-    public void getValueUpdatedCache() {
-        // First ensure there's nothing in cache
-        boolean val = mSettingsCache.getValue(KEY_SYSTEM_URI_TEST1, 0);
-        assertFalse(val);
-
-        mSettingsCache.setKeyCache(Collections.singletonMap(KEY_SYSTEM_URI_TEST1, true));
-        val = mSettingsCache.getValue(KEY_SYSTEM_URI_TEST1, 0);
-        assertTrue(val);
-    }
-
-    @Test
-    public void multipleListenersSingleKey() {
-        SettingsCache.OnChangeListener secondListener = mock(SettingsCache.OnChangeListener.class);
-        mSettingsCache.register(KEY_SYSTEM_URI_TEST1, secondListener);
-
-        mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST1);
-        verify(mChangeListener, times(1)).onSettingsChanged(true);
-        verify(secondListener, times(1)).onSettingsChanged(true);
-    }
-
-    @Test
-    public void singleListenerMultipleKeys() {
-        SettingsCache.OnChangeListener secondListener = mock(SettingsCache.OnChangeListener.class);
-        mSettingsCache.register(KEY_SYSTEM_URI_TEST2, secondListener);
-
-        mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST1);
-        mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST2);
-        verify(mChangeListener, times(1)).onSettingsChanged(true);
-        verify(secondListener, times(1)).onSettingsChanged(true);
-    }
-
-    @Test
-    public void sameListenerMultipleKeys() {
-        SettingsCache.OnChangeListener secondListener = mock(SettingsCache.OnChangeListener.class);
-        mSettingsCache.register(KEY_SYSTEM_URI_TEST2, mChangeListener);
-
-        mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST1);
-        mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST2);
-        verify(mChangeListener, times(2)).onSettingsChanged(true);
-        verify(secondListener, times(0)).onSettingsChanged(true);
-    }
-}
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 86217d7..10023b4 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -138,10 +138,11 @@
         mContext = context;
 
         mInvariantDeviceProfile = InvariantDeviceProfile.INSTANCE.get(context);
-        mIconProvider =  new IconProvider(context, Themes.isThemedIconEnabled(context));
+        mIconProvider = new IconProvider(context, Themes.isThemedIconEnabled(context));
         mIconCache = new IconCache(mContext, mInvariantDeviceProfile,
                 iconCacheFileName, mIconProvider);
-        mModel = new LauncherModel(context, this, mIconCache, new AppFilter(mContext));
+        mModel = new LauncherModel(context, this, mIconCache, new AppFilter(mContext),
+                iconCacheFileName != null);
         mOnTerminateCallback.add(mIconCache::close);
     }
 
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 9ebec0a..f38f662 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -126,10 +126,12 @@
         }
     };
 
-    LauncherModel(Context context, LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
+    LauncherModel(Context context, LauncherAppState app, IconCache iconCache, AppFilter appFilter,
+            boolean isPrimaryInstance) {
         mApp = app;
         mBgAllAppsList = new AllAppsList(iconCache, appFilter);
-        mModelDelegate = ModelDelegate.newInstance(context, app, mBgAllAppsList, mBgDataModel);
+        mModelDelegate = ModelDelegate.newInstance(context, app, mBgAllAppsList, mBgDataModel,
+                isPrimaryInstance);
     }
 
     public ModelDelegate getModelDelegate() {
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 2f9b5af..108091c 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -108,6 +108,8 @@
     // relative scroll position unchanged in updateCurrentPageScroll. Cleared when snapping to a
     // page.
     protected int mCurrentPageScrollDiff;
+    // The current page the PagedView is scrolling over on it's way to the destination page.
+    protected int mCurrentScrollOverPage;
 
     @ViewDebug.ExportedProperty(category = "launcher")
     protected int mNextPage = INVALID_PAGE;
@@ -180,6 +182,7 @@
 
         mScroller = new OverScroller(context, SCROLL);
         mCurrentPage = 0;
+        mCurrentScrollOverPage = 0;
 
         final ViewConfiguration configuration = ViewConfiguration.get(context);
         mTouchSlop = configuration.getScaledTouchSlop();
@@ -437,6 +440,7 @@
         }
         int prevPage = overridePrevPage != INVALID_PAGE ? overridePrevPage : mCurrentPage;
         mCurrentPage = validateNewPage(currentPage);
+        mCurrentScrollOverPage = mCurrentPage;
         updateCurrentPageScroll();
         notifyPageSwitchListener(prevPage);
         invalidate();
@@ -557,9 +561,11 @@
                 if (newPos < mMinScroll && oldPos >= mMinScroll) {
                     mEdgeGlowLeft.onAbsorb((int) mScroller.getCurrVelocity());
                     mScroller.abortAnimation();
+                    onEdgeAbsorbingScroll();
                 } else if (newPos > mMaxScroll && oldPos <= mMaxScroll) {
                     mEdgeGlowRight.onAbsorb((int) mScroller.getCurrVelocity());
                     mScroller.abortAnimation();
+                    onEdgeAbsorbingScroll();
                 }
             }
 
@@ -577,6 +583,7 @@
             sendScrollAccessibilityEvent();
             int prevPage = mCurrentPage;
             mCurrentPage = validateNewPage(mNextPage);
+            mCurrentScrollOverPage = mCurrentPage;
             mNextPage = INVALID_PAGE;
             notifyPageSwitchListener(prevPage);
 
@@ -766,7 +773,8 @@
                 childStart += primaryDimension + getChildGap();
 
                 // This makes sure that the space is added after the page, not after each panel
-                if (i % panelCount == panelCount - 1) {
+                int lastPanel = mIsRtl ? 0 : panelCount - 1;
+                if (i % panelCount == lastPanel) {
                     childStart += mPageSpacing;
                 }
             }
@@ -837,6 +845,7 @@
     public void onViewRemoved(View child) {
         super.onViewRemoved(child);
         mCurrentPage = validateNewPage(mCurrentPage);
+        mCurrentScrollOverPage = mCurrentPage;
         dispatchPageCountChanged();
     }
 
@@ -1408,6 +1417,20 @@
 
     protected void onNotSnappingToPageInFreeScroll() { }
 
+    /**
+     * Called when the view edges absorb part of the scroll. Subclasses can override this
+     * to provide custom behavior during animation.
+     */
+    protected void onEdgeAbsorbingScroll() {
+    }
+
+    /**
+     * Called when the current page closest to the center of the screen changes as part of the
+     * scroll. Subclasses can override this to provide custom behavior during scroll.
+     */
+    protected void onScrollOverPageChanged() {
+    }
+
     protected boolean shouldFlingForVelocity(int velocity) {
         float threshold = mAllowEasyFling ? mEasyFlingThresholdVelocity : mFlingThresholdVelocity;
         return Math.abs(velocity) > threshold;
@@ -1553,7 +1576,7 @@
         return getDisplacementFromScreenCenter(childIndex, screenCenter);
     }
 
-    private int getScreenCenter(int primaryScroll) {
+    protected int getScreenCenter(int primaryScroll) {
         float primaryScale = mOrientationHandler.getPrimaryScale(this);
         float primaryPivot =  mOrientationHandler.getPrimaryValue(getPivotX(), getPivotY());
         int pageOrientationSize = mOrientationHandler.getMeasuredSize(this);
@@ -1692,6 +1715,15 @@
     }
 
     @Override
+    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+        int newDestinationPage = getDestinationPage();
+        if (newDestinationPage >= 0 && newDestinationPage != mCurrentScrollOverPage) {
+            mCurrentScrollOverPage = newDestinationPage;
+            onScrollOverPageChanged();
+        }
+    }
+
+    @Override
     public CharSequence getAccessibilityClassName() {
         // Some accessibility services have special logic for ScrollView. Since we provide same
         // accessibility info as ScrollView, inform the service to handle use the same way.
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index a21c8e3..54b2c96 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -338,15 +338,17 @@
         int paddingBottom = grid.cellLayoutBottomPaddingPx;
 
         int panelCount = getPanelCount();
+        int rightPanelModulus = mIsRtl ? 0 : panelCount - 1;
+        int leftPanelModulus = mIsRtl ? panelCount - 1 : 0;
         int numberOfScreens = mScreenOrder.size();
         for (int i = 0; i < numberOfScreens; i++) {
             int paddingLeft = paddingLeftRight;
             int paddingRight = paddingLeftRight;
             if (panelCount > 1) {
-                if (i % panelCount == 0) { // left side panel
+                if (i % panelCount == leftPanelModulus) {
                     paddingLeft = paddingLeftRight;
                     paddingRight = 0;
-                } else if (i % panelCount == panelCount - 1) { // right side panel
+                } else if (i % panelCount == rightPanelModulus) {
                     paddingLeft = 0;
                     paddingRight = paddingLeftRight;
                 } else { // middle panel
@@ -857,18 +859,13 @@
         mWorkspaceScreens.remove(emptyScreenId);
         mScreenOrder.removeValue(emptyScreenId);
 
-        int newScreenId = LauncherSettings.Settings.call(getContext().getContentResolver(),
-                LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
-                .getInt(LauncherSettings.Settings.EXTRA_VALUE);
-
-
-        // When two panel home is enabled and the last page (the page on the right) doesn't
-        // have any items, then Launcher database doesn't know about this page because it was added
-        // by Launcher::bindAddScreens but wasn't inserted into the database. LauncherSettings's
-        // generate new screen ID method will return the ID for the left page,
-        // so we need to increment it.
-        if (isTwoPanelEnabled() && emptyScreenId == EXTRA_EMPTY_SCREEN_ID && newScreenId % 2 == 1) {
-            newScreenId++;
+        int newScreenId = -1;
+        // Launcher database isn't aware of empty pages that are already bound, so we need to
+        // skip those IDs manually.
+        while (newScreenId == -1 || mWorkspaceScreens.containsKey(newScreenId)) {
+            newScreenId = LauncherSettings.Settings.call(getContext().getContentResolver(),
+                    LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
+                    .getInt(LauncherSettings.Settings.EXTRA_VALUE);
         }
 
         mWorkspaceScreens.put(newScreenId, cl);
@@ -2453,21 +2450,32 @@
         }
 
         int nextPage = getNextPage();
-        if (layout == null && !isPageInTransition()) {
-            layout = verifyInsidePage(nextPage + (mIsRtl ? 1 : -1), Math.min(centerX, d.x), d.y);
+        IntSet pageIndexesToVerify = IntSet.wrap(nextPage - 1, nextPage + 1);
+        if (isTwoPanelEnabled()) {
+            // If two panel is enabled, users can also drag items to nextPage + 2
+            pageIndexesToVerify.add(nextPage + 2);
         }
 
-        if (layout == null && !isPageInTransition()) {
-            layout = verifyInsidePage(nextPage + (mIsRtl ? -1 : 1), Math.max(centerX, d.x), d.y);
+        int touchX = (int) Math.min(centerX, d.x);
+        int touchY = d.y;
+
+        // Go through the pages and check if the dragged item is inside one of them
+        for (int pageIndex : pageIndexesToVerify) {
+            if (layout != null || isPageInTransition()) {
+                break;
+            }
+            layout = verifyInsidePage(pageIndex, touchX, touchY);
         }
 
-        // If two panel is enabled, users can also drag items to currentPage + 2
-        if (isTwoPanelEnabled() && layout == null && !isPageInTransition()) {
-            layout = verifyInsidePage(nextPage + (mIsRtl ? -2 : 2), Math.max(centerX, d.x), d.y);
-        }
-
-        // Always pick the current page.
+        // If the dragged item isn't located in one of the pages above, the icon will stay on the
+        // current screen. For two panel pick the closest panel on the current screen,
+        // on one panel just choose the current page.
         if (layout == null && nextPage >= 0 && nextPage < getPageCount()) {
+            if (isTwoPanelEnabled()) {
+                nextPage = getScreenCenter(getScrollX()) > touchX
+                        ? (mIsRtl ? nextPage + 1 : nextPage) // left side
+                        : (mIsRtl ? nextPage : nextPage + 1); // right side
+            }
             layout = (CellLayout) getChildAt(nextPage);
         }
         if (layout != mDragTargetLayout) {
diff --git a/src/com/android/launcher3/model/DeviceGridState.java b/src/com/android/launcher3/model/DeviceGridState.java
index 761053d..e2df16d 100644
--- a/src/com/android/launcher3/model/DeviceGridState.java
+++ b/src/com/android/launcher3/model/DeviceGridState.java
@@ -25,10 +25,15 @@
 import android.content.SharedPreferences;
 import android.text.TextUtils;
 
+import androidx.annotation.IntDef;
+
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
+import com.android.launcher3.util.IntSet;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Locale;
 import java.util.Objects;
 
@@ -41,13 +46,23 @@
     public static final String KEY_HOTSEAT_COUNT = "migration_src_hotseat_count";
     public static final String KEY_DEVICE_TYPE = "migration_src_device_type";
 
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({TYPE_PHONE, TYPE_MULTI_DISPLAY, TYPE_TABLET})
+    public @interface DeviceType{}
     public static final int TYPE_PHONE = 0;
     public static final int TYPE_MULTI_DISPLAY = 1;
     public static final int TYPE_TABLET = 2;
 
+    private static final IntSet COMPATIBLE_TYPES = IntSet.wrap(TYPE_PHONE, TYPE_MULTI_DISPLAY);
+
+    public static boolean deviceTypeCompatible(@DeviceType int typeA, @DeviceType int typeB) {
+        return typeA == typeB
+                || (COMPATIBLE_TYPES.contains(typeA) && COMPATIBLE_TYPES.contains(typeB));
+    }
+
     private final String mGridSizeString;
     private final int mNumHotseat;
-    private final int mDeviceType;
+    private final @DeviceType int mDeviceType;
 
     public DeviceGridState(InvariantDeviceProfile idp) {
         mGridSizeString = String.format(Locale.ENGLISH, "%d,%d", idp.numColumns, idp.numRows);
@@ -69,7 +84,7 @@
     /**
      * Returns the device type for the grid
      */
-    public int getDeviceType() {
+    public @DeviceType int getDeviceType() {
         return mDeviceType;
     }
 
@@ -109,7 +124,7 @@
         if (o == null || getClass() != o.getClass()) return false;
         DeviceGridState that = (DeviceGridState) o;
         return mNumHotseat == that.mNumHotseat
-                && mDeviceType == that.mDeviceType
+                && deviceTypeCompatible(mDeviceType, that.mDeviceType)
                 && Objects.equals(mGridSizeString, that.mGridSizeString);
     }
 }
diff --git a/src/com/android/launcher3/model/ModelDelegate.java b/src/com/android/launcher3/model/ModelDelegate.java
index 13ec1ec..765141a 100644
--- a/src/com/android/launcher3/model/ModelDelegate.java
+++ b/src/com/android/launcher3/model/ModelDelegate.java
@@ -40,19 +40,21 @@
      * Creates and initializes a new instance of the delegate
      */
     public static ModelDelegate newInstance(
-            Context context, LauncherAppState app, AllAppsList appsList, BgDataModel dataModel) {
+            Context context, LauncherAppState app, AllAppsList appsList, BgDataModel dataModel,
+            boolean isPrimaryInstance) {
         ModelDelegate delegate = Overrides.getObject(
                 ModelDelegate.class, context, R.string.model_delegate_class);
-
         delegate.mApp = app;
         delegate.mAppsList = appsList;
         delegate.mDataModel = dataModel;
+        delegate.mIsPrimaryInstance = isPrimaryInstance;
         return delegate;
     }
 
     protected LauncherAppState mApp;
     protected AllAppsList mAppsList;
     protected BgDataModel mDataModel;
+    protected boolean mIsPrimaryInstance;
 
     public ModelDelegate() { }
 
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index 8d02a4a..d59429d 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -84,9 +84,10 @@
     }
 
     private static boolean performRestore(Context context, DatabaseHelper helper) {
-        if (new DeviceGridState(LauncherAppState.getIDP(context)).getDeviceType()
-                != Utilities.getPrefs(context).getInt(RESTORED_DEVICE_TYPE, TYPE_PHONE)) {
-            // DO not restore if the device types are different
+        if (!DeviceGridState.deviceTypeCompatible(
+                new DeviceGridState(LauncherAppState.getIDP(context)).getDeviceType(),
+                Utilities.getPrefs(context).getInt(RESTORED_DEVICE_TYPE, TYPE_PHONE))) {
+            // DO NOT restore if the device types are incompatible.
             return false;
         }
         SQLiteDatabase db = helper.getWritableDatabase();
diff --git a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
index 4d63218..b06b8a1 100644
--- a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
+++ b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
@@ -20,6 +20,7 @@
 import static android.view.View.GONE;
 import static android.view.View.VISIBLE;
 
+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;
 
@@ -29,6 +30,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.net.Uri;
@@ -44,6 +46,7 @@
 import android.view.MenuItem;
 import android.view.View;
 import android.widget.EditText;
+import android.widget.Toast;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -57,12 +60,15 @@
 import androidx.preference.SwitchPreference;
 
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.config.FlagTogglerPrefUi;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
+import com.android.launcher3.util.OnboardingPrefs;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -104,6 +110,7 @@
         initFlags();
         loadPluginPrefs();
         maybeAddSandboxCategory();
+        addOnboardingPrefsCatergory();
 
         if (getActivity() != null) {
             getActivity().setTitle("Developer Options");
@@ -153,6 +160,15 @@
             }
         });
 
+        if (getArguments() != null) {
+            String filter = getArguments().getString(EXTRA_FRAGMENT_ARG_KEY);
+            // Normally EXTRA_FRAGMENT_ARG_KEY is used to highlight the preference with the given
+            // key. This is a slight variation where we instead filter by the human-readable titles.
+            if (filter != null) {
+                filterBox.setText(filter);
+            }
+        }
+
         View listView = getListView();
         final int bottomPadding = listView.getPaddingBottom();
         listView.setOnApplyWindowInsetsListener((v, insets) -> {
@@ -355,6 +371,28 @@
         sandboxCategory.addPreference(launchSandboxModeTutorialPreference);
     }
 
+    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 = Utilities.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);
+        }
+    }
+
     private String toName(String action) {
         String str = action.replace("com.android.systemui.action.PLUGIN_", "")
                 .replace("com.android.launcher3.action.PLUGIN_", "");
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index 064d808..f1fd439 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -503,9 +503,11 @@
             if (dp.isLandscape) {
                 splitOffset.x = splitInfo.leftTopBounds.width() +
                         splitInfo.visualDividerBounds.width();
+                splitOffset.y = 0;
             } else {
                 splitOffset.y = splitInfo.leftTopBounds.height() +
                         splitInfo.visualDividerBounds.height();
+                splitOffset.x = 0;
             }
         }
     }
diff --git a/src/com/android/launcher3/util/OnboardingPrefs.java b/src/com/android/launcher3/util/OnboardingPrefs.java
index cf1467a..5ba0d30 100644
--- a/src/com/android/launcher3/util/OnboardingPrefs.java
+++ b/src/com/android/launcher3/util/OnboardingPrefs.java
@@ -39,6 +39,14 @@
     public static final String SEARCH_EDU_SEEN = "launcher.search_edu_seen";
     public static final String SEARCH_SNACKBAR_COUNT = "launcher.keyboard_snackbar_count";
     public static final String TASKBAR_EDU_SEEN = "launcher.taskbar_edu_seen";
+    // When adding a new key, add it here as well, to be able to reset it from Developer Options.
+    public static final Map<String, String[]> ALL_PREF_KEYS = Map.of(
+            "All Apps Bounce", new String[] { HOME_BOUNCE_SEEN, HOME_BOUNCE_COUNT },
+            "Hybrid Hotseat Education", new String[] { HOTSEAT_DISCOVERY_TIP_COUNT,
+                    HOTSEAT_LONGPRESS_TIP_SEEN },
+            "Search Education", new String[] { SEARCH_EDU_SEEN, SEARCH_SNACKBAR_COUNT },
+            "Taskbar Education", new String[] { TASKBAR_EDU_SEEN }
+    );
 
     /**
      * Events that either have happened or have not (booleans).
diff --git a/src_plugins/com/android/systemui/plugins/OneSearch.java b/src_plugins/com/android/systemui/plugins/OneSearch.java
index 6d57c19..8bd0b75 100644
--- a/src_plugins/com/android/systemui/plugins/OneSearch.java
+++ b/src_plugins/com/android/systemui/plugins/OneSearch.java
@@ -16,8 +16,7 @@
 
 package com.android.systemui.plugins;
 
-import android.graphics.Bitmap;
-import android.text.Spanned;
+import android.os.Parcelable;
 
 import com.android.systemui.plugins.annotations.ProvidesInterface;
 
@@ -29,7 +28,7 @@
 @ProvidesInterface(action = OneSearch.ACTION, version = OneSearch.VERSION)
 public interface OneSearch extends Plugin {
     String ACTION = "com.android.systemui.action.PLUGIN_ONE_SEARCH";
-    int VERSION = 2;
+    int VERSION = 3;
 
     /**
      * Get the content provider warmed up.
@@ -37,23 +36,8 @@
     void warmUp();
 
     /**
-     * Get the suggests for the query.
+     * Get the suggest search target list for the query.
      * @param query The query to get the search suggests for.
      */
-    ArrayList<Spanned> getSuggests(String query);
-
-    /**
-     * Get the image bitmap for the suggest.
-     * @param suggest The suggest to get the image bitmap for.
-     */
-    Bitmap getImageBitmap(Spanned suggest);
-
-    /**
-     * Get the subtitle for the suggest.
-     * @param suggest The suggest to get the subtitle for.
-     */
-    String getSubtitle(Spanned suggest);
-
-    /** Clear any cached data or storage used in search. */
-    void clear();
+    ArrayList<Parcelable> getSuggests(Parcelable query);
 }
diff --git a/robolectric_tests/src/com/android/launcher3/settings/SettingsActivityTest.java b/tests/src/com/android/launcher3/settings/SettingsActivityTest.java
similarity index 96%
rename from robolectric_tests/src/com/android/launcher3/settings/SettingsActivityTest.java
rename to tests/src/com/android/launcher3/settings/SettingsActivityTest.java
index 3271812..1c205f0 100644
--- a/robolectric_tests/src/com/android/launcher3/settings/SettingsActivityTest.java
+++ b/tests/src/com/android/launcher3/settings/SettingsActivityTest.java
@@ -55,6 +55,7 @@
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -75,6 +76,7 @@
     }
 
     @Test
+    @Ignore  // b/199309785
     public void testSettings_aboutTap_launchesActivity() {
         ActivityScenario.launch(SettingsActivity.class);
         onView(withId(R.id.recycler_view)).perform(
@@ -88,6 +90,7 @@
     }
 
     @Test
+    @Ignore  // b/199309785
     public void testSettings_developerOptionsTap_launchesActivityWithFragment() {
         PluginPrefs.setHasPlugins(mApplicationContext);
         ActivityScenario.launch(SettingsActivity.class);
@@ -100,6 +103,7 @@
     }
 
     @Test
+    @Ignore  // b/199309785
     public void testSettings_aboutScreenIntent() {
         Bundle fragmentArgs = new Bundle();
         fragmentArgs.putString(ARG_PREFERENCE_ROOT, "about_screen");
@@ -114,6 +118,7 @@
     }
 
     @Test
+    @Ignore  // b/199309785
     public void testSettings_developerOptionsFragmentIntent() {
         Intent intent = new Intent(mApplicationContext, SettingsActivity.class)
                 .putExtra(EXTRA_FRAGMENT, DeveloperOptionsFragment.class.getName());
@@ -125,6 +130,7 @@
     }
 
     @Test
+    @Ignore  // b/199309785
     public void testSettings_intentWithUnknownFragment() {
         String fragmentClass = PreferenceFragmentCompat.class.getName();
         Intent intent = new Intent(mApplicationContext, SettingsActivity.class)
@@ -139,6 +145,7 @@
     }
 
     @Test
+    @Ignore  // b/199309785
     public void testSettings_backButtonFinishesActivity() {
         Bundle fragmentArgs = new Bundle();
         fragmentArgs.putString(ARG_PREFERENCE_ROOT, "about_screen");