Merge changes from topic "new-task-snapshots-ub-launcher3-master" into ub-launcher3-master

* changes:
  Toggle loading state based on config_lowResTaskSnapshotScale
  Add config to enable snapshot preloading
diff --git a/Android.bp b/Android.bp
index cb695df..e132854 100644
--- a/Android.bp
+++ b/Android.bp
@@ -47,3 +47,14 @@
     },
     static_libs: ["libprotobuf-java-lite"],
 }
+
+java_library {
+    name: "LauncherPluginLib",
+
+    static_libs: ["PluginCoreLib"],
+
+    srcs: ["src_plugins/**/*.java"],
+
+    sdk_version: "current",
+    min_sdk_version: "28",
+}
diff --git a/Android.mk b/Android.mk
index c066a12..9cfcf17 100644
--- a/Android.mk
+++ b/Android.mk
@@ -17,24 +17,6 @@
 LOCAL_PATH := $(call my-dir)
 
 #
-# Build rule for plugin lib (needed to write a plugin).
-#
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT2_ONLY := true
-LOCAL_MODULE_TAGS := optional
-LOCAL_STATIC_JAVA_LIBRARIES:= PluginCoreLib
-
-LOCAL_SRC_FILES := \
-    $(call all-java-files-under, src_plugins)
-
-LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 28
-LOCAL_MODULE := LauncherPluginLib
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
-#
 # Build rule for Launcher3 dependencies lib.
 #
 include $(CLEAR_VARS)
diff --git a/proguard.flags b/proguard.flags
index e556c94..37b8093 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -50,4 +50,13 @@
 -dontwarn android.graphics.**
 
 # Ignore warnings for hidden utility classes referenced from the shared lib
--dontwarn com.android.internal.util.**
\ No newline at end of file
+-dontwarn com.android.internal.util.**
+
+################ Do not optimize recents lib #############
+-keep class com.android.systemui.** {
+  *;
+}
+
+-keep class com.android.quickstep.** {
+  *;
+}
diff --git a/quickstep/recents_ui_overrides/res/layout/overview_panel.xml b/quickstep/recents_ui_overrides/res/layout/overview_panel.xml
index 7f1425b..a572cad 100644
--- a/quickstep/recents_ui_overrides/res/layout/overview_panel.xml
+++ b/quickstep/recents_ui_overrides/res/layout/overview_panel.xml
@@ -14,12 +14,17 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.quickstep.views.LauncherRecentsView
+<com.android.launcher3.InsettableFrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:theme="@style/HomeScreenElementTheme"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:clipChildren="false"
-    android:clipToPadding="false"
-    android:accessibilityPaneTitle="@string/accessibility_recent_apps"
-    android:visibility="invisible" />
\ No newline at end of file
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+    <com.android.quickstep.views.LauncherRecentsView
+        android:id="@+id/overview_panel_recents"
+        android:theme="@style/HomeScreenElementTheme"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        android:accessibilityPaneTitle="@string/accessibility_recent_apps"
+        android:visibility="invisible" />
+</com.android.launcher3.InsettableFrameLayout>
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
index 03862db..1f5228a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
@@ -19,6 +19,7 @@
 import static com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController.isTouchOverHotseat;
 
 import android.view.MotionEvent;
+import android.view.animation.Interpolator;
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.util.PendingAnimation;
@@ -74,12 +75,12 @@
      * @param duration how long the animation should be
      * @return the animation
      */
-    PendingAnimation createSwipeDownToTaskAppAnimation(long duration) {
+    PendingAnimation createSwipeDownToTaskAppAnimation(long duration, Interpolator interpolator) {
         mRecentsView.setCurrentPage(mRecentsView.getPageNearestToCenterOfScreen());
         TaskView taskView = mRecentsView.getCurrentPageTaskView();
         if (taskView == null) {
             throw new IllegalStateException("There is no task view to animate to.");
         }
-        return mRecentsView.createTaskLauncherAnimation(taskView, duration);
+        return mRecentsView.createTaskLaunchAnimation(taskView, duration, interpolator);
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index 32855d7..947a861 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -191,9 +191,8 @@
 
             mEndDisplacement = -mTaskBeingDragged.getHeight();
         } else {
-            mPendingAnimation = mRecentsView.createTaskLauncherAnimation(
-                    mTaskBeingDragged, maxDuration);
-            mPendingAnimation.anim.setInterpolator(Interpolators.ZOOM_IN);
+            mPendingAnimation = mRecentsView.createTaskLaunchAnimation(
+                    mTaskBeingDragged, maxDuration, Interpolators.ZOOM_IN);
 
             mTempCords[1] = mTaskBeingDragged.getHeight();
             dl.getDescendantCoordRelativeToSelf(mTaskBeingDragged, mTempCords);
@@ -203,8 +202,8 @@
         if (mCurrentAnimation != null) {
             mCurrentAnimation.setOnCancelRunnable(null);
         }
-        mCurrentAnimation = AnimatorPlaybackController
-                .wrap(mPendingAnimation.anim, maxDuration, this::clearState);
+        mCurrentAnimation = AnimatorPlaybackController.wrap(
+                mPendingAnimation.anim, maxDuration, this::clearState);
         onUserControlledAnimationCreated(mCurrentAnimation);
         mCurrentAnimation.getTarget().addListener(this);
         mCurrentAnimation.dispatchOnStart();
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
index b71fede..5a9c2fe 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
@@ -70,9 +70,6 @@
     // if the aspect ratio of the target is smaller than the aspect ratio of the source rect. In
     // app window coordinates.
     private final RectF mSourceWindowClipInsets = new RectF();
-    // The insets to be used for clipping the app window. For live tile, we don't transform the clip
-    // relative to the target rect.
-    private final RectF mSourceWindowClipInsetsForLiveTile = new RectF();
     // The clip rect in source app window coordinates. The app window surface will only be drawn
     // within these bounds. This clip rect starts at the full mSourceStackBounds, and insets by
     // mSourceWindowClipInsets as the transform progress goes to 1.
@@ -149,7 +146,6 @@
                 Math.max(scaledTargetRect.top, 0),
                 Math.max(mSourceStackBounds.width() - scaledTargetRect.right, 0),
                 Math.max(mSourceStackBounds.height() - scaledTargetRect.bottom, 0));
-        mSourceWindowClipInsetsForLiveTile.set(mSourceWindowClipInsets);
         mSourceRect.set(scaledTargetRect);
     }
 
@@ -252,14 +248,12 @@
     private void updateClipRect(TransformParams params) {
         // Don't clip past progress > 1.
         float progress = Math.min(1, params.mProgress);
-        final RectF sourceWindowClipInsets = params.mForLiveTile
-                ? mSourceWindowClipInsetsForLiveTile : mSourceWindowClipInsets;
-        mCurrentClipRectF.left = sourceWindowClipInsets.left * progress;
-        mCurrentClipRectF.top = sourceWindowClipInsets.top * progress;
+        mCurrentClipRectF.left = mSourceWindowClipInsets.left * progress;
+        mCurrentClipRectF.top = mSourceWindowClipInsets.top * progress;
         mCurrentClipRectF.right =
-                mSourceStackBounds.width() - (sourceWindowClipInsets.right * progress);
+                mSourceStackBounds.width() - (mSourceWindowClipInsets.right * progress);
         mCurrentClipRectF.bottom =
-                mSourceStackBounds.height() - (sourceWindowClipInsets.bottom * progress);
+                mSourceStackBounds.height() - (mSourceWindowClipInsets.bottom * progress);
     }
 
     public RectF getCurrentRectWithInsets() {
@@ -400,7 +394,6 @@
         private float mOffsetScale;
         private @Nullable RectF mCurrentRect;
         private float mTargetAlpha;
-        private boolean mForLiveTile;
         private float mCornerRadius;
         private boolean mLauncherOnTop;
         private RemoteAnimationTargets mTargetSet;
@@ -412,7 +405,6 @@
             mOffsetScale = 1;
             mCurrentRect = null;
             mTargetAlpha = 1;
-            mForLiveTile = false;
             mCornerRadius = -1;
             mLauncherOnTop = false;
         }
@@ -477,16 +469,6 @@
         }
 
         /**
-         * Specifies whether we should clip the source window based on
-         * {@link AppWindowAnimationHelper#mSourceWindowClipInsetsForLiveTile} rather than
-         * {@link AppWindowAnimationHelper#mSourceWindowClipInsets} as {@link #mProgress} goes to 1.
-         */
-        public TransformParams setForLiveTile(boolean forLiveTile) {
-            mForLiveTile = forLiveTile;
-            return this;
-        }
-
-        /**
          * If true, sets the crop = null and layer = Integer.MAX_VALUE for targets that don't match
          * {@link #mTargetSet}.targetMode. (Currently only does this when live tiles are enabled.)
          */
@@ -539,10 +521,6 @@
             return mTargetAlpha;
         }
 
-        public boolean isForLiveTile() {
-            return mForLiveTile;
-        }
-
         public float getCornerRadius() {
             return mCornerRadius;
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
index d705cc0..3e106aa 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -201,8 +201,7 @@
             if (tv.isRunningTask()) {
                 mTransformParams.setProgress(1 - progress)
                         .setCurrentRect(null)
-                        .setSyncTransactionApplier(mSyncTransactionApplier)
-                        .setForLiveTile(true);
+                        .setSyncTransactionApplier(mSyncTransactionApplier);
                 mAppWindowAnimationHelper.applyTransform(mTransformParams);
             } else {
                 redrawLiveTile(true);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index fe78a84..e34e74c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
@@ -76,6 +76,7 @@
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.animation.Interpolator;
 import android.widget.ListView;
 
 import androidx.annotation.Nullable;
@@ -1599,7 +1600,8 @@
         return anim;
     }
 
-    public PendingAnimation createTaskLauncherAnimation(TaskView tv, long duration) {
+    public PendingAnimation createTaskLaunchAnimation(
+            TaskView tv, long duration, Interpolator interpolator) {
         if (FeatureFlags.IS_STUDIO_BUILD && mPendingAnimation != null) {
             throw new IllegalStateException("Another pending animation is still running");
         }
@@ -1612,7 +1614,6 @@
         int targetSysUiFlags = tv.getThumbnail().getSysUiStatusNavFlags();
         final boolean[] passedOverviewThreshold = new boolean[] {false};
         ValueAnimator progressAnim = ValueAnimator.ofFloat(0, 1);
-        progressAnim.setInterpolator(LINEAR);
         progressAnim.addUpdateListener(animator -> {
             // Once we pass a certain threshold, update the sysui flags to match the target
             // tasks' flags
@@ -1638,7 +1639,7 @@
         appWindowAnimationHelper.prepareAnimation(mActivity.getDeviceProfile(), true /* isOpening */);
         AnimatorSet anim = createAdjacentPageAnimForTaskLaunch(tv, appWindowAnimationHelper);
         anim.play(progressAnim);
-        anim.setDuration(duration);
+        anim.setDuration(duration).setInterpolator(interpolator);
 
         Consumer<Boolean> onTaskLaunchFinish = this::onTaskLaunched;
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
index 8b7ce10..dce92ff 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
@@ -21,6 +21,7 @@
 import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
 
@@ -280,11 +281,10 @@
     }
 
     public AnimatorPlaybackController createLaunchAnimationForRunningTask() {
-        final PendingAnimation pendingAnimation =
-                getRecentsView().createTaskLauncherAnimation(this, RECENTS_LAUNCH_DURATION);
-        pendingAnimation.anim.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
-        AnimatorPlaybackController currentAnimation = AnimatorPlaybackController
-                .wrap(pendingAnimation.anim, RECENTS_LAUNCH_DURATION, null);
+        final PendingAnimation pendingAnimation = getRecentsView().createTaskLaunchAnimation(
+                this, RECENTS_LAUNCH_DURATION, TOUCH_RESPONSE_INTERPOLATOR);
+        AnimatorPlaybackController currentAnimation = AnimatorPlaybackController.wrap(
+                pendingAnimation.anim, RECENTS_LAUNCH_DURATION);
         currentAnimation.setEndAction(() -> {
             pendingAnimation.finish(true, Touch.SWIPE);
             launchTask(false);
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 15503b8..07d2381 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -194,7 +194,7 @@
 
         if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(this)) {
             // Overview is above all other launcher elements, including qsb, so move it to the top.
-            getOverviewPanel().bringToFront();
+            getOverviewPanelContainer().bringToFront();
         }
     }
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index d5ce734..fe830d2 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -228,15 +228,13 @@
             // Reset the state manager, when changing the interaction mode
             mLauncher.getStateManager().goToState(OVERVIEW, false /* animate */);
             mPendingAnimation = mOverviewPortraitStateTouchHelper
-                    .createSwipeDownToTaskAppAnimation(maxAccuracy);
-            mPendingAnimation.anim.setInterpolator(Interpolators.LINEAR);
-
+                    .createSwipeDownToTaskAppAnimation(maxAccuracy, Interpolators.LINEAR);
             Runnable onCancelRunnable = () -> {
                 cancelPendingAnim();
                 clearState();
             };
-            mCurrentAnimation = AnimatorPlaybackController.wrap(mPendingAnimation.anim, maxAccuracy,
-                    onCancelRunnable);
+            mCurrentAnimation = AnimatorPlaybackController.wrap(
+                    mPendingAnimation.anim, maxAccuracy, onCancelRunnable);
             mLauncher.getStateManager().setCurrentUserControlledAnimation(mCurrentAnimation);
             totalShift = LayoutUtils.getShelfTrackingDistance(mLauncher,
                     mLauncher.getDeviceProfile());
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 00adfbe..21a4918 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -81,12 +81,9 @@
             mWindowThresholdCrossed = windowThresholdCrossed;
             UI_HELPER_EXECUTOR.execute(() -> {
                 mController.setAnimationTargetsBehindSystemBars(!windowThresholdCrossed);
-                if (mShouldMinimizeSplitScreen && windowThresholdCrossed) {
-                    // NOTE: As a workaround for conflicting animations (Launcher animating the task
-                    // leash, and SystemUI resizing the docked stack, which resizes the task), we
-                    // currently only set the minimized mode, and not the inverse.
-                    // TODO: Synchronize the minimize animation with the launcher animation
-                    mController.setSplitScreenMinimized(windowThresholdCrossed);
+                SystemUiProxy p = SystemUiProxy.INSTANCE.getNoCreate();
+                if (p != null && mShouldMinimizeSplitScreen) {
+                    p.setSplitScreenMinimized(windowThresholdCrossed);
                 }
             });
         }
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 101bb07..458d6a9 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -295,4 +295,15 @@
             }
         }
     }
+
+    @Override
+    public void setSplitScreenMinimized(boolean minimized) {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.setSplitScreenMinimized(minimized);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call stopScreenPinning", e);
+            }
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
index d8b10b6..7d52571 100644
--- a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
+++ b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
@@ -15,6 +15,8 @@
  */
 package com.android.quickstep.util;
 
+import static com.android.launcher3.config.FeatureFlags.ENABLE_LSQ_VELOCITY_PROVIDER;
+
 import android.content.Context;
 import android.content.res.Resources;
 import android.view.MotionEvent;
@@ -85,7 +87,8 @@
         mForcePauseTimeout = new Alarm();
         mForcePauseTimeout.setOnAlarmListener(alarm -> updatePaused(true /* isPaused */));
         mMakePauseHarderToTrigger = makePauseHarderToTrigger;
-        mVelocityProvider = new LinearVelocityProvider(axis);
+        mVelocityProvider = ENABLE_LSQ_VELOCITY_PROVIDER.get()
+                ? new LSqVelocityProvider(axis) : new LinearVelocityProvider(axis);
     }
 
     /**
@@ -106,8 +109,6 @@
     /**
      * Computes velocity and acceleration to determine whether the motion is paused.
      * @param ev The motion being tracked.
-     *
-     * TODO: Use historical positions as well, e.g. {@link MotionEvent#getHistoricalY(int, int)}.
      */
     public void addPosition(MotionEvent ev) {
         addPosition(ev, 0);
@@ -248,4 +249,137 @@
             mPreviousPosition = null;
         }
     }
+
+    /**
+     * Java implementation of {@link android.view.VelocityTracker} using the Least Square (deg 2)
+     * algorithm.
+     */
+    private static class LSqVelocityProvider implements VelocityProvider {
+
+        // Maximum age of a motion event to be considered when calculating the velocity.
+        private static final long HORIZON_MS = 100;
+        // Number of samples to keep.
+        private static final int HISTORY_SIZE = 20;
+
+        // Position history are stored in a circular array
+        private final float[] mHistoricTimes = new float[HISTORY_SIZE];
+        private final float[] mHistoricPos = new float[HISTORY_SIZE];
+        private int mHistoryCount = 0;
+        private int mHistoryStart = 0;
+
+        private final int mAxis;
+
+        LSqVelocityProvider(int axis) {
+            mAxis = axis;
+        }
+
+        @Override
+        public void clear() {
+            mHistoryCount = mHistoryStart = 0;
+        }
+
+        private void addPositionAndTime(float eventTime, float eventPosition) {
+            mHistoricTimes[mHistoryStart] = eventTime;
+            mHistoricPos[mHistoryStart] = eventPosition;
+            mHistoryStart++;
+            if (mHistoryStart >= HISTORY_SIZE) {
+                mHistoryStart = 0;
+            }
+            mHistoryCount = Math.min(HISTORY_SIZE, mHistoryCount + 1);
+        }
+
+        @Override
+        public Float addMotionEvent(MotionEvent ev, int pointer) {
+            // Add all historic points
+            int historyCount = ev.getHistorySize();
+            for (int i = 0; i < historyCount; i++) {
+                addPositionAndTime(
+                        ev.getHistoricalEventTime(i), ev.getHistoricalAxisValue(mAxis, pointer, i));
+            }
+
+            // Start index for the last position (about to be added)
+            int eventStartIndex = mHistoryStart;
+            addPositionAndTime(ev.getEventTime(), ev.getAxisValue(mAxis, pointer));
+            return solveUnweightedLeastSquaresDeg2(eventStartIndex);
+        }
+
+        /**
+         * Solves the instantaneous velocity.
+         * Based on solveUnweightedLeastSquaresDeg2 in VelocityTracker.cpp
+         */
+        private Float solveUnweightedLeastSquaresDeg2(final int pointPos) {
+            final float eventTime = mHistoricTimes[pointPos];
+
+            float sxi = 0, sxiyi = 0, syi = 0, sxi2 = 0, sxi3 = 0, sxi2yi = 0, sxi4 = 0;
+            int count = 0;
+            for (int i = 0; i < mHistoryCount; i++) {
+                int index = pointPos - i;
+                if (index < 0) {
+                    index += HISTORY_SIZE;
+                }
+
+                float time = mHistoricTimes[index];
+                float age = eventTime - time;
+                if (age > HORIZON_MS) {
+                    break;
+                }
+                count++;
+                float xi = -age;
+
+                float yi = mHistoricPos[index];
+                float xi2 = xi * xi;
+                float xi3 = xi2 * xi;
+                float xi4 = xi3 * xi;
+                float xiyi = xi * yi;
+                float xi2yi = xi2 * yi;
+
+                sxi += xi;
+                sxi2 += xi2;
+                sxiyi += xiyi;
+                sxi2yi += xi2yi;
+                syi += yi;
+                sxi3 += xi3;
+                sxi4 += xi4;
+            }
+
+            if (count < 3) {
+                // Too few samples
+                if (count == 2) {
+                    int endPos = pointPos - 1;
+                    if (endPos < 0) {
+                        endPos += HISTORY_SIZE;
+                    }
+                    float denominator = eventTime - mHistoricTimes[endPos];
+                    if (denominator != 0) {
+                        return (eventTime - mHistoricPos[endPos]) / denominator;
+
+                    }
+                }
+                return null;
+            }
+
+            float Sxx = sxi2 - sxi * sxi / count;
+            float Sxy = sxiyi - sxi * syi / count;
+            float Sxx2 = sxi3 - sxi * sxi2 / count;
+            float Sx2y = sxi2yi - sxi2 * syi / count;
+            float Sx2x2 = sxi4 - sxi2 * sxi2 / count;
+
+            float denominator = Sxx * Sx2x2 - Sxx2 * Sxx2;
+            if (denominator == 0) {
+                // division by 0 when computing velocity
+                return null;
+            }
+            // Compute a
+            // float numerator = Sx2y*Sxx - Sxy*Sxx2;
+
+            // Compute b
+            float numerator = Sxy * Sx2x2 - Sx2y * Sxx2;
+            float b = numerator / denominator;
+
+            // Compute c
+            // float c = syi/count - b * sxi/count - a * sxi2/count;
+
+            return b;
+        }
+    }
 }
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 724af66..ecfdb55 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -45,7 +45,6 @@
 import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
 import com.android.quickstep.views.RecentsView;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -53,21 +52,10 @@
 @LargeTest
 @RunWith(AndroidJUnit4.class)
 public class TaplTestsQuickstep extends AbstractQuickStepTest {
-    private int mLauncherPid;
-
     @Before
     public void setUp() throws Exception {
-        mLauncherPid = 0;
         super.setUp();
         TaplTestsLauncher3.initialize(this);
-        mLauncherPid = mLauncher.getPid();
-    }
-
-    @After
-    public void teardown() {
-        if (mLauncherPid != 0) {
-            assertEquals("Launcher crashed, pid mismatch:", mLauncherPid, mLauncher.getPid());
-        }
     }
 
     private void startTestApps() throws Exception {
diff --git a/res/layout/launcher.xml b/res/layout/launcher.xml
index cca899b..6c66897 100644
--- a/res/layout/launcher.xml
+++ b/res/layout/launcher.xml
@@ -44,9 +44,8 @@
             layout="@layout/hotseat" />
 
         <include
-            android:id="@+id/overview_panel"
-            layout="@layout/overview_panel"
-            android:visibility="gone" />
+            android:id="@+id/overview_panel_container"
+            layout="@layout/overview_panel"/>
 
         <!-- Keep these behind the workspace so that they are not visible when
          we go into AllApps -->
diff --git a/res/layout/overview_panel.xml b/res/layout/overview_panel.xml
index bdd5d23..7fff711 100644
--- a/res/layout/overview_panel.xml
+++ b/res/layout/overview_panel.xml
@@ -14,7 +14,9 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<Space
+<FrameLayout
       xmlns:android="http://schemas.android.com/apk/res/android"
+      android:id="@+id/overview_panel_recents"
       android:layout_width="0dp"
-      android:layout_height="0dp" />
\ No newline at end of file
+      android:layout_height="0dp"
+      android:visibility="gone" />
\ No newline at end of file
diff --git a/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java b/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java
index 1ed4bca..c426dc5 100644
--- a/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java
@@ -65,7 +65,7 @@
         };
 
         mIdp.numHotseatIcons = 3;
-        new GridSizeMigrationTask(mContext, mDb, mValidPackages, 5, 3)
+        new GridSizeMigrationTask(mContext, mDb, mValidPackages, false, 5, 3)
                 .migrateHotseat();
         // First item is dropped as it has the least weight.
         verifyHotseat(hotseatItems[1], hotseatItems[3], hotseatItems[4]);
@@ -82,7 +82,7 @@
         };
 
         mIdp.numHotseatIcons = 3;
-        new GridSizeMigrationTask(mContext, mDb, mValidPackages, 5, 3)
+        new GridSizeMigrationTask(mContext, mDb, mValidPackages, false, 5, 3)
                 .migrateHotseat();
         // First item is dropped as it has the least weight.
         verifyHotseat(hotseatItems[1], hotseatItems[3], hotseatItems[4]);
@@ -127,7 +127,7 @@
                 {  5,  2, -1,  6},
         }});
 
-        new GridSizeMigrationTask(mContext, mDb, mValidPackages,
+        new GridSizeMigrationTask(mContext, mDb, mValidPackages, false,
                 new Point(4, 4), new Point(3, 3)).migrateWorkspace();
 
         // Column 2 and row 2 got removed.
@@ -147,7 +147,7 @@
                 {  5,  2, -1,  6},
         }});
 
-        new GridSizeMigrationTask(mContext, mDb, mValidPackages,
+        new GridSizeMigrationTask(mContext, mDb, mValidPackages, false,
                 new Point(4, 4), new Point(3, 3)).migrateWorkspace();
 
         // Items in the second column get moved to new screen
@@ -172,7 +172,7 @@
                 {  3,  1, -1,  4},
         }});
 
-        new GridSizeMigrationTask(mContext, mDb, mValidPackages,
+        new GridSizeMigrationTask(mContext, mDb, mValidPackages, false,
                 new Point(4, 4), new Point(3, 3)).migrateWorkspace();
 
         // Items in the second column of the first screen should get placed on the 3rd
@@ -204,7 +204,7 @@
                 {  5,  2, -1,  6},
         }});
 
-        new GridSizeMigrationTask(mContext, mDb, mValidPackages,
+        new GridSizeMigrationTask(mContext, mDb, mValidPackages, false,
                 new Point(4, 4), new Point(3, 3)).migrateWorkspace();
 
         // Items in the second column of the first screen should get placed on a new screen.
@@ -235,7 +235,7 @@
                 {  5,  2,  7, -1},
         }}, 0);
 
-        new GridSizeMigrationTask(mContext, mDb, mValidPackages,
+        new GridSizeMigrationTask(mContext, mDb, mValidPackages, false,
                 new Point(4, 4), new Point(3, 4)).migrateWorkspace();
 
         // Items in the second column of the first screen should get placed on a new screen.
@@ -262,7 +262,7 @@
                 {  5,  6,  7, -1},
         }}, 0);
 
-        new GridSizeMigrationTask(mContext, mDb, mValidPackages,
+        new GridSizeMigrationTask(mContext, mDb, mValidPackages, false,
                 new Point(4, 4), new Point(3, 3)).migrateWorkspace();
 
         // Items in the second column of the first screen should get placed on a new screen.
@@ -282,7 +282,7 @@
      *            represent the workspace grid.
      */
     private void verifyWorkspace(int[][][] ids) {
-        IntArray allScreens = getWorkspaceScreenIds(mDb);
+        IntArray allScreens = getWorkspaceScreenIds(mDb, LauncherSettings.Favorites.TABLE_NAME);
         assertEquals(ids.length, allScreens.size());
         int total = 0;
 
@@ -351,7 +351,7 @@
         private final LinkedList<Point> mPoints;
 
         public MultiStepMigrationTaskVerifier(int... points) {
-            super(null, null, null);
+            super(null, null, null, false);
 
             mPoints = new LinkedList<>();
             for (int i = 0; i < points.length; i += 2) {
diff --git a/robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java b/robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java
index 7fa3ee9..6e41a4f 100644
--- a/robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java
@@ -52,6 +52,7 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.LauncherRoboTestRunner;
@@ -91,7 +92,7 @@
                 SCREEN, CELLX, CELLY, RESTORED, INTENT
         });
 
-        mLoaderCursor = new LoaderCursor(mCursor, mApp);
+        mLoaderCursor = new LoaderCursor(mCursor, LauncherSettings.Favorites.CONTENT_URI, mApp);
         mLoaderCursor.allUsers.put(0, Process.myUserHandle());
     }
 
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 857db8e..2ad84b9 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -47,6 +47,7 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.graphics.IconShape;
+import com.android.launcher3.graphics.LauncherPreviewRenderer;
 import com.android.launcher3.util.ConfigMonitor;
 import com.android.launcher3.util.DefaultDisplay;
 import com.android.launcher3.util.DefaultDisplay.Info;
@@ -156,6 +157,11 @@
 
     @TargetApi(23)
     private InvariantDeviceProfile(Context context) {
+        if (context instanceof LauncherPreviewRenderer.PreviewContext) {
+            throw new IllegalArgumentException(
+                    "PreviewContext is passed into this IDP constructor");
+        }
+
         String gridName = Utilities.getPrefs(context).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false)
                 ? Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null)
                 : null;
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 3fc8de2..33f5a95 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -80,6 +80,7 @@
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.OvershootInterpolator;
+import android.widget.FrameLayout;
 import android.widget.Toast;
 
 import androidx.annotation.Nullable;
@@ -273,6 +274,7 @@
 
     // UI and state for the overview panel
     private View mOverviewPanel;
+    private FrameLayout mOverviewPanelContainer;
 
     @Thunk
     boolean mWorkspaceLoading = true;
@@ -1143,7 +1145,8 @@
         mFocusHandler = mDragLayer.getFocusIndicatorHelper();
         mWorkspace = mDragLayer.findViewById(R.id.workspace);
         mWorkspace.initParentViews(mDragLayer);
-        mOverviewPanel = findViewById(R.id.overview_panel);
+        mOverviewPanel = findViewById(R.id.overview_panel_recents);
+        mOverviewPanelContainer = findViewById(R.id.overview_panel_container);
         mHotseat = findViewById(R.id.hotseat);
 
         mLauncherView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
@@ -1386,6 +1389,10 @@
         return (T) mOverviewPanel;
     }
 
+    public FrameLayout getOverviewPanelContainer() {
+        return mOverviewPanelContainer;
+    }
+
     public DropTargetBar getDropTargetBar() {
         return mDropTargetBar;
     }
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index d77285d..2f38037 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -27,6 +27,8 @@
 import android.os.Handler;
 import android.util.Log;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.icons.IconProvider;
@@ -55,12 +57,12 @@
     private final IconCache mIconCache;
     private final WidgetPreviewLoader mWidgetCache;
     private final InvariantDeviceProfile mInvariantDeviceProfile;
-    private final SecureSettingsObserver mNotificationDotsObserver;
 
-    private final InstallSessionTracker mInstallSessionTracker;
-    private final SimpleBroadcastReceiver mModelChangeReceiver;
-    private final SafeCloseable mCalendarChangeTracker;
-    private final SafeCloseable mUserChangeListener;
+    private SecureSettingsObserver mNotificationDotsObserver;
+    private InstallSessionTracker mInstallSessionTracker;
+    private SimpleBroadcastReceiver mModelChangeReceiver;
+    private SafeCloseable mCalendarChangeTracker;
+    private SafeCloseable mUserChangeListener;
 
     public static LauncherAppState getInstance(final Context context) {
         return INSTANCE.get(context);
@@ -74,15 +76,8 @@
         return mContext;
     }
 
-    private LauncherAppState(Context context) {
-        Log.v(Launcher.TAG, "LauncherAppState initiated");
-        Preconditions.assertUIThread();
-        mContext = context;
-
-        mInvariantDeviceProfile = InvariantDeviceProfile.INSTANCE.get(mContext);
-        mIconCache = new IconCache(mContext, mInvariantDeviceProfile);
-        mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache);
-        mModel = new LauncherModel(this, mIconCache, AppFilter.newInstance(mContext));
+    public LauncherAppState(Context context) {
+        this(context, LauncherFiles.APP_ICONS_DB);
 
         mModelChangeReceiver = new SimpleBroadcastReceiver(mModel::onBroadcastIntent);
 
@@ -123,6 +118,17 @@
         }
     }
 
+    public LauncherAppState(Context context, @Nullable String iconCacheFileName) {
+        Log.v(Launcher.TAG, "LauncherAppState initiated");
+        Preconditions.assertUIThread();
+        mContext = context;
+
+        mInvariantDeviceProfile = InvariantDeviceProfile.INSTANCE.get(context);
+        mIconCache = new IconCache(mContext, mInvariantDeviceProfile, iconCacheFileName);
+        mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache);
+        mModel = new LauncherModel(this, mIconCache, AppFilter.newInstance(mContext));
+    }
+
     protected void onNotificationSettingsChanged(boolean areNotificationDotsEnabled) {
         if (areNotificationDotsEnabled) {
             NotificationListener.requestRebind(new ComponentName(
@@ -148,11 +154,19 @@
      * Call from Application.onTerminate(), which is not guaranteed to ever be called.
      */
     public void onTerminate() {
-        mContext.unregisterReceiver(mModelChangeReceiver);
+        if (mModelChangeReceiver != null) {
+            mContext.unregisterReceiver(mModelChangeReceiver);
+        }
         mContext.getSystemService(LauncherApps.class).unregisterCallback(mModel);
-        mInstallSessionTracker.unregister();
-        mCalendarChangeTracker.close();
-        mUserChangeListener.close();
+        if (mInstallSessionTracker != null) {
+            mInstallSessionTracker.unregister();
+        }
+        if (mCalendarChangeTracker != null) {
+            mCalendarChangeTracker.close();
+        }
+        if (mUserChangeListener != null) {
+            mUserChangeListener.close();
+        }
         CustomWidgetManager.INSTANCE.get(mContext).setWidgetRefreshCallback(null);
 
         if (mNotificationDotsObserver != null) {
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index f618fe1..e61b7a8 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -153,7 +153,7 @@
 
     public void onPackagesRemoved(UserHandle user, String... packages) {
         int op = PackageUpdatedTask.OP_REMOVE;
-        FileLog.d(TAG, "package removed received " + String.join("," + packages));
+        FileLog.d(TAG, "package removed received " + TextUtils.join(",", packages));
         enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packages));
     }
 
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 49831f6..216c221 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -93,15 +93,26 @@
         public static final String TABLE_NAME = "favorites";
 
         /**
-         * Backup table created when when the favorites table is modified during grid migration
+         * Backup table created when the favorites table is modified during grid migration
          */
         public static final String BACKUP_TABLE_NAME = "favorites_bakup";
 
         /**
-         * The content:// style URL for this table
+         * Temporary table used specifically for grid migrations during wallpaper preview
          */
-        public static final Uri CONTENT_URI = Uri.parse("content://" +
-                LauncherProvider.AUTHORITY + "/" + TABLE_NAME);
+        public static final String PREVIEW_TABLE_NAME = "favorites_preview";
+
+        /**
+         * The content:// style URL for "favorites" table
+         */
+        public static final Uri CONTENT_URI = Uri.parse("content://"
+                + LauncherProvider.AUTHORITY + "/" + TABLE_NAME);
+
+        /**
+         * The content:// style URL for "favorites_preview" table
+         */
+        public static final Uri PREVIEW_CONTENT_URI = Uri.parse("content://"
+                + LauncherProvider.AUTHORITY + "/" + PREVIEW_TABLE_NAME);
 
         /**
          * The content:// style URL for a given row, identified by its id.
@@ -111,8 +122,8 @@
          * @return The unique content URL for the specified row.
          */
         public static Uri getContentUri(int id) {
-            return Uri.parse("content://" + LauncherProvider.AUTHORITY +
-                    "/" + TABLE_NAME + "/" + id);
+            return Uri.parse("content://" + LauncherProvider.AUTHORITY
+                    + "/" + TABLE_NAME + "/" + id);
         }
 
         /**
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 16be391..36440c9 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -74,6 +74,10 @@
     public static final int VERTICAL_SWIPE_INDICATOR = 1 << 5;
     public static final int RECENTS_CLEAR_ALL_BUTTON = 1 << 6;
 
+    /** Mask of all the items that are contained in the apps view. */
+    public static final int APPS_VIEW_ITEM_MASK =
+            HOTSEAT_SEARCH_BOX | ALL_APPS_HEADER | ALL_APPS_HEADER_EXTRA | ALL_APPS_CONTENT;
+
     protected static final int FLAG_MULTI_PAGE = 1 << 0;
     protected static final int FLAG_DISABLE_ACCESSIBILITY = 1 << 1;
     protected static final int FLAG_DISABLE_RESTORE = 1 << 2;
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 6aa3efc..e49ff30 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -2,6 +2,7 @@
 
 import static com.android.launcher3.LauncherState.ALL_APPS_CONTENT;
 import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
+import static com.android.launcher3.LauncherState.APPS_VIEW_ITEM_MASK;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.LauncherState.VERTICAL_SWIPE_INDICATOR;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
@@ -203,6 +204,8 @@
         boolean hasHeaderExtra = (visibleElements & ALL_APPS_HEADER_EXTRA) != 0;
         boolean hasAllAppsContent = (visibleElements & ALL_APPS_CONTENT) != 0;
 
+        boolean hasAnyVisibleItem = (visibleElements & APPS_VIEW_ITEM_MASK) != 0;
+
         Interpolator allAppsFade = builder.getInterpolator(ANIM_ALL_APPS_FADE, LINEAR);
         Interpolator headerFade = builder.getInterpolator(ANIM_ALL_APPS_HEADER_FADE, allAppsFade);
         setter.setViewAlpha(mAppsView.getContentView(), hasAllAppsContent ? 1 : 0, allAppsFade);
@@ -213,6 +216,8 @@
 
         setter.setInt(mScrimView, ScrimView.DRAG_HANDLE_ALPHA,
                 (visibleElements & VERTICAL_SWIPE_INDICATOR) != 0 ? 255 : 0, allAppsFade);
+
+        setter.setViewAlpha(mAppsView, hasAnyVisibleItem ? 1 : 0, allAppsFade);
     }
 
     public AnimatorListenerAdapter getProgressAnimatorListener() {
diff --git a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
index db4bef0..1d32d1d 100644
--- a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
+++ b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
@@ -40,8 +40,16 @@
         return isAccessibilityEnabled(context);
     }
 
-    public static void sendCustomAccessibilityEvent(View target, int type, @Nullable String text) {
-        if (isObservedEventType(target.getContext(), type)) {
+    /**
+     *
+     * @param target The view the accessibility event is initialized on.
+     *               If null, this method has no effect.
+     * @param type See TYPE_ constants defined in {@link AccessibilityEvent}.
+     * @param text Optional text to add to the event, which will be announced to the user.
+     */
+    public static void sendCustomAccessibilityEvent(@Nullable View target, int type,
+            @Nullable String text) {
+        if (target != null && isObservedEventType(target.getContext(), type)) {
             AccessibilityEvent event = AccessibilityEvent.obtain(type);
             target.onInitializeAccessibilityEvent(event);
             if (!TextUtils.isEmpty(text)) {
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 6413044..3d8a9d7 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -135,6 +135,10 @@
             "ENABLE_UNIVERSAL_SMARTSPACE", false,
             "Replace Smartspace with a version rendered by System UI.");
 
+    public static final BooleanFlag ENABLE_LSQ_VELOCITY_PROVIDER = getDebugFlag(
+            "ENABLE_LSQ_VELOCITY_PROVIDER", false,
+            "Use Least Square algorithm for motion pause detection.");
+
     public static void initialize(Context context) {
         synchronized (sDebugFlags) {
             for (DebugFlag flag : sDebugFlags) {
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index ddb88dc..e33d89f 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -346,7 +346,7 @@
         }
 
         mInfo.title = newTitle;
-        mInfo.setOption(FLAG_MANUAL_FOLDER_NAME, mFolderName.isEnteredCompose(),
+        mInfo.setOption(FLAG_MANUAL_FOLDER_NAME, getAcceptedSuggestionIndex() < 0,
                 mLauncher.getModelWriter());
         mFolderIcon.onTitleChanged(newTitle);
         mLauncher.getModelWriter().updateItemInDatabase(mInfo);
@@ -437,11 +437,11 @@
         }
         mItemsInvalidated = true;
         mInfo.addListener(this);
+        mPreviousLabel = mInfo.title.toString();
+        mIsPreviousLabelSuggested = !mInfo.hasOption(FLAG_MANUAL_FOLDER_NAME);
 
         if (!isEmpty(mInfo.title)) {
             mFolderName.setText(mInfo.title);
-            mPreviousLabel = mInfo.title.toString();
-            mIsPreviousLabelSuggested = !mInfo.hasOption(FLAG_MANUAL_FOLDER_NAME);
             mFolderName.setHint(null);
         } else {
             mFolderName.setText("");
diff --git a/src/com/android/launcher3/folder/FolderNameEditText.java b/src/com/android/launcher3/folder/FolderNameEditText.java
index 7e11b18..edf2c70 100644
--- a/src/com/android/launcher3/folder/FolderNameEditText.java
+++ b/src/com/android/launcher3/folder/FolderNameEditText.java
@@ -102,13 +102,6 @@
         mEnteredCompose = value;
     }
 
-    protected boolean isEnteredCompose() {
-        if (DEBUG) {
-            Log.d(TAG, "isEnteredCompose " + mEnteredCompose);
-        }
-        return mEnteredCompose;
-    }
-
     private class FolderNameEditTextInputConnection extends InputConnectionWrapper {
 
         FolderNameEditTextInputConnection(InputConnection target, boolean mutable) {
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index a429af2..0927b26 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -20,14 +20,19 @@
 import static android.view.View.VISIBLE;
 
 import static com.android.launcher3.config.FeatureFlags.ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER;
+import static com.android.launcher3.config.FeatureFlags.MULTI_DB_GRID_MIRATION_ALGO;
+import static com.android.launcher3.model.GridSizeMigrationTask.needsToMigrate;
 import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
 import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
 import android.annotation.TargetApi;
 import android.app.Fragment;
 import android.appwidget.AppWidgetHostView;
 import android.content.Context;
+import android.content.ContextWrapper;
 import android.content.Intent;
+import android.content.pm.ShortcutInfo;
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -70,17 +75,31 @@
 import com.android.launcher3.icons.BaseIconFactory;
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.BitmapRenderer;
+import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.model.AllAppsList;
 import com.android.launcher3.model.BgDataModel;
 import com.android.launcher3.model.BgDataModel.Callbacks;
+import com.android.launcher3.model.GridSizeMigrationTask;
+import com.android.launcher3.model.GridSizeMigrationTaskV2;
 import com.android.launcher3.model.LoaderResults;
+import com.android.launcher3.model.LoaderTask;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.model.WidgetsModel;
+import com.android.launcher3.pm.InstallSessionHelper;
+import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.views.BaseDragLayer;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
@@ -101,6 +120,81 @@
 
     private static final String TAG = "LauncherPreviewRenderer";
 
+    /**
+     * Context used just for preview. It also provides a few objects (e.g. UserCache) just for
+     * preview purposes.
+     */
+    public static class PreviewContext extends ContextWrapper {
+
+        private static final Set<MainThreadInitializedObject> WHITELIST = new HashSet<>(
+                Arrays.asList(UserCache.INSTANCE, InstallSessionHelper.INSTANCE,
+                        LauncherAppState.INSTANCE, InvariantDeviceProfile.INSTANCE));
+
+        private final InvariantDeviceProfile mIdp;
+        private final Map<MainThreadInitializedObject, Object> mObjectMap = new HashMap<>();
+        private final ConcurrentLinkedQueue<LauncherIconsForPreview> mIconPool =
+                new ConcurrentLinkedQueue<>();
+
+        public PreviewContext(Context base, InvariantDeviceProfile idp) {
+            super(base);
+            mIdp = idp;
+        }
+
+        @Override
+        public Context getApplicationContext() {
+            return this;
+        }
+
+        /**
+         * Find a cached object from mObjectMap if we have already created one. If not, generate
+         * an object using the provider.
+         */
+        public <T> T getObject(MainThreadInitializedObject<T> mainThreadInitializedObject,
+                MainThreadInitializedObject.ObjectProvider<T> provider) {
+            if (!WHITELIST.contains(mainThreadInitializedObject)) {
+                throw new IllegalStateException("Leaking unknown objects");
+            }
+            if (mainThreadInitializedObject == LauncherAppState.INSTANCE) {
+                throw new IllegalStateException(
+                        "Should not use MainThreadInitializedObject to initialize this with "
+                                + "PreviewContext");
+            }
+            if (mainThreadInitializedObject == InvariantDeviceProfile.INSTANCE) {
+                return (T) mIdp;
+            }
+            if (mObjectMap.containsKey(mainThreadInitializedObject)) {
+                return (T) mObjectMap.get(mainThreadInitializedObject);
+            }
+            T t = provider.get(this);
+            mObjectMap.put(mainThreadInitializedObject, t);
+            return t;
+        }
+
+        public LauncherIcons newLauncherIcons(Context context, boolean shapeDetection) {
+            LauncherIconsForPreview launcherIconsForPreview = mIconPool.poll();
+            if (launcherIconsForPreview != null) {
+                return launcherIconsForPreview;
+            }
+            return new LauncherIconsForPreview(context, mIdp.fillResIconDpi, mIdp.iconBitmapSize,
+                    -1 /* poolId */, shapeDetection);
+        }
+
+        private final class LauncherIconsForPreview extends LauncherIcons {
+
+            private LauncherIconsForPreview(Context context, int fillResIconDpi, int iconBitmapSize,
+                    int poolId, boolean shapeDetection) {
+                super(context, fillResIconDpi, iconBitmapSize, poolId, shapeDetection);
+            }
+
+            @Override
+            public void recycle() {
+                // Clear any temporary state variables
+                clear();
+                mIconPool.offer(this);
+            }
+        }
+    }
+
     private final Handler mUiHandler;
     private final Context mContext;
     private final InvariantDeviceProfile mIdp;
@@ -282,15 +376,28 @@
 
         private void renderScreenShot(Canvas canvas) {
             if (ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER.get()) {
-                final LauncherModel launcherModel = LauncherAppState.getInstance(
-                        mContext).getModel();
-                final WorkspaceItemsInfoFetcher fetcher = new WorkspaceItemsInfoFetcher();
-                launcherModel.enqueueModelUpdateTask(fetcher);
-                WorkspaceResult workspaceResult;
-                try {
-                    workspaceResult = fetcher.mTask.get(5, TimeUnit.SECONDS);
-                } catch (InterruptedException | ExecutionException | TimeoutException e) {
-                    Log.d(TAG, "Error fetching workspace items info", e);
+                boolean needsToMigrate = needsToMigrate(mContext, mIdp);
+                boolean success = false;
+                if (needsToMigrate) {
+                    success = MULTI_DB_GRID_MIRATION_ALGO.get()
+                            ? GridSizeMigrationTaskV2.migrateGridIfNeeded(mContext, mIdp)
+                            : GridSizeMigrationTask.migrateGridIfNeeded(mContext, mIdp);
+                }
+
+                WorkspaceFetcher fetcher;
+                if (needsToMigrate && success) {
+                    LauncherAppState appForPreview = new LauncherAppState(
+                            new PreviewContext(mContext, mIdp), null /* iconCacheFileName */);
+                    fetcher = new WorkspaceItemsInfoFromPreviewFetcher(appForPreview);
+                    MODEL_EXECUTOR.execute(fetcher);
+                } else {
+                    fetcher = new WorkspaceItemsInfoFetcher();
+                    LauncherAppState.getInstance(mContext).getModel().enqueueModelUpdateTask(
+                            (LauncherModel.ModelUpdateTask) fetcher);
+                }
+                WorkspaceResult workspaceResult = fetcher.get();
+
+                if (workspaceResult == null) {
                     return;
                 }
 
@@ -379,8 +486,13 @@
         }
     }
 
-    private static class WorkspaceItemsInfoFetcher implements Callable<WorkspaceResult>,
-            LauncherModel.ModelUpdateTask {
+    private static void measureView(View view, int width, int height) {
+        view.measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY));
+        view.layout(0, 0, width, height);
+    }
+
+    private static class WorkspaceItemsInfoFetcher implements LauncherModel.ModelUpdateTask,
+            WorkspaceFetcher {
 
         private final FutureTask<WorkspaceResult> mTask = new FutureTask<>(this);
 
@@ -399,6 +511,11 @@
         }
 
         @Override
+        public FutureTask<WorkspaceResult> getTask() {
+            return mTask;
+        }
+
+        @Override
         public void run() {
             mTask.run();
         }
@@ -417,9 +534,45 @@
         }
     }
 
-    private static void measureView(View view, int width, int height) {
-        view.measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY));
-        view.layout(0, 0, width, height);
+    private static class WorkspaceItemsInfoFromPreviewFetcher extends LoaderTask implements
+            WorkspaceFetcher {
+
+        private final FutureTask<WorkspaceResult> mTask = new FutureTask<>(this);
+
+        WorkspaceItemsInfoFromPreviewFetcher(LauncherAppState app) {
+            super(app, null, new BgDataModel(), null);
+        }
+
+        @Override
+        public FutureTask<WorkspaceResult> getTask() {
+            return mTask;
+        }
+
+        @Override
+        public void run() {
+            mTask.run();
+        }
+
+        @Override
+        public WorkspaceResult call() throws Exception {
+            List<ShortcutInfo> allShortcuts = new ArrayList<>();
+            loadWorkspace(allShortcuts, LauncherSettings.Favorites.PREVIEW_CONTENT_URI);
+            return new WorkspaceResult(mBgDataModel.workspaceItems, mBgDataModel.appWidgets,
+                    mBgDataModel.widgetsModel);
+        }
+    }
+
+    private interface WorkspaceFetcher extends Runnable, Callable<WorkspaceResult> {
+        FutureTask<WorkspaceResult> getTask();
+
+        default WorkspaceResult get() {
+            try {
+                return getTask().get(5, TimeUnit.SECONDS);
+            } catch (InterruptedException | ExecutionException | TimeoutException e) {
+                Log.d(TAG, "Error fetching workspace items info", e);
+                return null;
+            }
+        }
     }
 
     private static class WorkspaceResult {
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 804acc3..f27c9da 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -81,9 +81,13 @@
 
     private int mPendingIconRequestCount = 0;
 
-    public IconCache(Context context, InvariantDeviceProfile inv) {
-        super(context, LauncherFiles.APP_ICONS_DB, MODEL_EXECUTOR.getLooper(),
-                inv.fillResIconDpi, inv.iconBitmapSize, true /* inMemoryCache */);
+    public IconCache(Context context, InvariantDeviceProfile idp) {
+        this(context, idp, LauncherFiles.APP_ICONS_DB);
+    }
+
+    public IconCache(Context context, InvariantDeviceProfile idp, String dbFileName) {
+        super(context, dbFileName, MODEL_EXECUTOR.getLooper(),
+                idp.fillResIconDpi, idp.iconBitmapSize, true /* inMemoryCache */);
         mComponentWithLabelCachingLogic = new ComponentCachingLogic(context, false);
         mLauncherActivityInfoCachingLogic = LauncherActivityCachingLogic.newInstance(context);
         mShortcutCachingLogic = new ShortcutCachingLogic();
diff --git a/src/com/android/launcher3/icons/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java
index cbd7c53..bf7897e 100644
--- a/src/com/android/launcher3/icons/LauncherIcons.java
+++ b/src/com/android/launcher3/icons/LauncherIcons.java
@@ -19,8 +19,8 @@
 import android.content.Context;
 
 import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.graphics.IconShape;
+import com.android.launcher3.graphics.LauncherPreviewRenderer;
 
 /**
  * Wrapper class to provide access to {@link BaseIconFactory} and also to provide pool of this class
@@ -41,6 +41,11 @@
      * avoid allocating new objects in many cases.
      */
     public static LauncherIcons obtain(Context context, boolean shapeDetection) {
+        if (context instanceof LauncherPreviewRenderer.PreviewContext) {
+            return ((LauncherPreviewRenderer.PreviewContext) context).newLauncherIcons(context,
+                    shapeDetection);
+        }
+
         int poolId;
         synchronized (sPoolSync) {
             if (sPool != null) {
@@ -52,7 +57,7 @@
             poolId = sPoolId;
         }
 
-        InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
+        InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(context);
         return new LauncherIcons(context, idp.fillResIconDpi, idp.iconBitmapSize, poolId,
                 shapeDetection);
     }
@@ -68,7 +73,7 @@
 
     private LauncherIcons next;
 
-    private LauncherIcons(Context context, int fillResIconDpi, int iconBitmapSize, int poolId,
+    protected LauncherIcons(Context context, int fillResIconDpi, int iconBitmapSize, int poolId,
             boolean shapeDetection) {
         super(context, fillResIconDpi, iconBitmapSize, shapeDetection);
         mPoolId = poolId;
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index 199d13f..afa3f6d 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -147,7 +147,7 @@
             fillIntentInfo(event.srcTarget[0], intent, userHandle);
         }
         ItemInfo info = (ItemInfo) v.getTag();
-        if (Utilities.IS_DEBUG_DEVICE && FeatureFlags.ENABLE_HYBRID_HOTSEAT.get()) {
+        if (info != null && Utilities.IS_DEBUG_DEVICE && FeatureFlags.ENABLE_HYBRID_HOTSEAT.get()) {
             FileLog.d(TAG, "appLaunch: packageName:" + info.getTargetComponent().getPackageName()
                     + ",isWorkApp:" + (info.user != null && !Process.myUserHandle().equals(
                     userHandle)) + ",launchLocation:" + info.container);
diff --git a/src/com/android/launcher3/model/GridBackupTable.java b/src/com/android/launcher3/model/GridBackupTable.java
index 07a7551..4a1bc4d 100644
--- a/src/com/android/launcher3/model/GridBackupTable.java
+++ b/src/com/android/launcher3/model/GridBackupTable.java
@@ -100,12 +100,24 @@
                     Process.myUserHandle()), 0);
             return false;
         }
+        return restoreIfBackupExists(Favorites.TABLE_NAME);
+    }
+
+    public boolean restoreToPreviewIfBackupExists() {
+        if (!tableExists(mDb, BACKUP_TABLE_NAME)) {
+            return false;
+        }
+
+        return restoreIfBackupExists(Favorites.PREVIEW_TABLE_NAME);
+    }
+
+    private boolean restoreIfBackupExists(String toTableName) {
         if (loadDBProperties() != STATE_SANITIZED) {
             return false;
         }
         long userSerial = UserCache.INSTANCE.get(mContext).getSerialNumberForUser(
                 Process.myUserHandle());
-        copyTable(mDb, BACKUP_TABLE_NAME, Favorites.TABLE_NAME, userSerial);
+        copyTable(mDb, BACKUP_TABLE_NAME, toTableName, userSerial);
         Log.d(TAG, "Backup table found");
         return true;
     }
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTask.java b/src/com/android/launcher3/model/GridSizeMigrationTask.java
index c35c4b9..3ba740d 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTask.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTask.java
@@ -3,6 +3,7 @@
 import static com.android.launcher3.LauncherSettings.Settings.EXTRA_VALUE;
 import static com.android.launcher3.Utilities.getPointString;
 import static com.android.launcher3.Utilities.parsePoint;
+import static com.android.launcher3.provider.LauncherDbUtils.copyTable;
 
 import android.content.ComponentName;
 import android.content.ContentValues;
@@ -14,6 +15,7 @@
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.graphics.Point;
+import android.os.SystemClock;
 import android.util.Log;
 import android.util.SparseArray;
 
@@ -29,6 +31,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.graphics.LauncherPreviewRenderer;
 import com.android.launcher3.pm.InstallSessionHelper;
 import com.android.launcher3.provider.LauncherDbUtils;
 import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
@@ -69,6 +72,7 @@
 
     private final SparseArray<ContentValues> mUpdateOperations = new SparseArray<>();
     private final HashSet<String> mValidPackages;
+    private final String mTableName;
 
     private final int mSrcX, mSrcY;
     private final int mTrgX, mTrgY;
@@ -78,10 +82,12 @@
     private final int mDestHotseatSize;
 
     protected GridSizeMigrationTask(Context context, SQLiteDatabase db,
-            HashSet<String> validPackages, Point sourceSize, Point targetSize) {
+            HashSet<String> validPackages, boolean usePreviewTable, Point sourceSize,
+            Point targetSize) {
         mContext = context;
         mDb = db;
         mValidPackages = validPackages;
+        mTableName = usePreviewTable ? Favorites.PREVIEW_TABLE_NAME : Favorites.TABLE_NAME;
 
         mSrcX = sourceSize.x;
         mSrcY = sourceSize.y;
@@ -97,10 +103,12 @@
     }
 
     protected GridSizeMigrationTask(Context context, SQLiteDatabase db,
-            HashSet<String> validPackages, int srcHotseatSize, int destHotseatSize) {
+            HashSet<String> validPackages, boolean usePreviewTable, int srcHotseatSize,
+            int destHotseatSize) {
         mContext = context;
         mDb = db;
         mValidPackages = validPackages;
+        mTableName = usePreviewTable ? Favorites.PREVIEW_TABLE_NAME : Favorites.TABLE_NAME;
 
         mSrcHotseatSize = srcHotseatSize;
 
@@ -120,7 +128,7 @@
         // Update items
         int updateCount = mUpdateOperations.size();
         for (int i = 0; i < updateCount; i++) {
-            mDb.update(Favorites.TABLE_NAME, mUpdateOperations.valueAt(i),
+            mDb.update(mTableName, mUpdateOperations.valueAt(i),
                     "_id=" + mUpdateOperations.keyAt(i), null);
         }
 
@@ -128,8 +136,8 @@
             if (DEBUG) {
                 Log.d(TAG, "Removing items: " + mEntryToRemove.toConcatString());
             }
-            mDb.delete(Favorites.TABLE_NAME, Utilities.createDbSelectionQuery(
-                    Favorites._ID, mEntryToRemove), null);
+            mDb.delete(mTableName, Utilities.createDbSelectionQuery(Favorites._ID, mEntryToRemove),
+                    null);
         }
 
         return updateCount > 0 || !mEntryToRemove.isEmpty();
@@ -182,8 +190,8 @@
     }
 
     @VisibleForTesting
-    static IntArray getWorkspaceScreenIds(SQLiteDatabase db) {
-        return LauncherDbUtils.queryIntArray(db, Favorites.TABLE_NAME, Favorites.SCREEN,
+    static IntArray getWorkspaceScreenIds(SQLiteDatabase db, String tableName) {
+        return LauncherDbUtils.queryIntArray(db, tableName, Favorites.SCREEN,
                 Favorites.CONTAINER + " = " + Favorites.CONTAINER_DESKTOP,
                 Favorites.SCREEN, Favorites.SCREEN);
     }
@@ -192,7 +200,7 @@
      * @return true if any DB change was made
      */
     protected boolean migrateWorkspace() throws Exception {
-        IntArray allScreens = getWorkspaceScreenIds(mDb);
+        IntArray allScreens = getWorkspaceScreenIds(mDb, mTableName);
         if (allScreens.isEmpty()) {
             throw new Exception("Unable to get workspace screens");
         }
@@ -244,12 +252,12 @@
     /**
      * Migrate a particular screen id.
      * Strategy:
-     *   1) For all possible combinations of row and column, pick the one which causes the least
-     *      data loss: {@link #tryRemove(int, int, int, ArrayList, float[])}
-     *   2) Maintain a list of all lost items before this screen, and add any new item lost from
-     *      this screen to that list as well.
-     *   3) If all those items from the above list can be placed on this screen, place them
-     *      (otherwise they are placed on a new screen).
+     *  1) For all possible combinations of row and column, pick the one which causes the least
+     *    data loss: {@link #tryRemove(int, int, int, ArrayList, float[])}
+     *  2) Maintain a list of all lost items before this screen, and add any new item lost from
+     *    this screen to that list as well.
+     *  3) If all those items from the above list can be placed on this screen, place them
+     *    (otherwise they are placed on a new screen).
      */
     protected void migrateScreen(int screenId) {
         // If we are migrating the first screen, do not touch the first row.
@@ -362,9 +370,9 @@
     /**
      * Tries the remove the provided row and column.
      *
-     * @param items all the items on the screen under operation
+     * @param items   all the items on the screen under operation
      * @param outLoss array of size 2. The first entry is filled with weight loss, and the second
-     * with the overall item movement.
+     *                with the overall item movement.
      */
     private ArrayList<DbEntry> tryRemove(int col, int row, int startY,
             ArrayList<DbEntry> items, float[] outLoss) {
@@ -379,13 +387,13 @@
 
         for (DbEntry item : items) {
             if ((item.cellX <= col && (item.spanX + item.cellX) > col)
-                || (item.cellY <= row && (item.spanY + item.cellY) > row)) {
+                    || (item.cellY <= row && (item.spanY + item.cellY) > row)) {
                 removedItems.add(item);
-                if (item.cellX >= col) item.cellX --;
-                if (item.cellY >= row) item.cellY --;
+                if (item.cellX >= col) item.cellX--;
+                if (item.cellY >= row) item.cellY--;
             } else {
-                if (item.cellX > col) item.cellX --;
-                if (item.cellY > row) item.cellY --;
+                if (item.cellX > col) item.cellX--;
+                if (item.cellY > row) item.cellY--;
                 finalItems.add(item);
                 occupied.markCells(item, true);
             }
@@ -438,9 +446,9 @@
         /**
          * Recursively finds a placement for the provided items.
          *
-         * @param index the position in {@link #itemsToPlace} to start looking at.
-         * @param weightLoss total weight loss upto this point
-         * @param moveCost total move cost upto this point
+         * @param index       the position in {@link #itemsToPlace} to start looking at.
+         * @param weightLoss  total weight loss upto this point
+         * @param moveCost    total move cost upto this point
          * @param itemsPlaced all the items already placed upto this point
          */
         public void find(int index, float weightLoss, float moveCost,
@@ -481,11 +489,11 @@
                         float newMoveCost = moveCost;
                         if (x != myX) {
                             me.cellX = x;
-                            newMoveCost ++;
+                            newMoveCost++;
                         }
                         if (y != myY) {
                             me.cellY = y;
-                            newMoveCost ++;
+                            newMoveCost++;
                         }
                         if (ignoreMove) {
                             newMoveCost = moveCost;
@@ -500,35 +508,35 @@
 
                         // Try resizing horizontally
                         if (myW > me.minSpanX && occupied.isRegionVacant(x, y, myW - 1, myH)) {
-                            me.spanX --;
+                            me.spanX--;
                             occupied.markCells(me, true);
                             // 1 extra move cost
                             find(index + 1, weightLoss, newMoveCost + 1, itemsIncludingMe);
                             occupied.markCells(me, false);
-                            me.spanX ++;
+                            me.spanX++;
                         }
 
                         // Try resizing vertically
                         if (myH > me.minSpanY && occupied.isRegionVacant(x, y, myW, myH - 1)) {
-                            me.spanY --;
+                            me.spanY--;
                             occupied.markCells(me, true);
                             // 1 extra move cost
                             find(index + 1, weightLoss, newMoveCost + 1, itemsIncludingMe);
                             occupied.markCells(me, false);
-                            me.spanY ++;
+                            me.spanY++;
                         }
 
                         // Try resizing horizontally & vertically
                         if (myH > me.minSpanY && myW > me.minSpanX &&
                                 occupied.isRegionVacant(x, y, myW - 1, myH - 1)) {
-                            me.spanX --;
-                            me.spanY --;
+                            me.spanX--;
+                            me.spanY--;
                             occupied.markCells(me, true);
                             // 2 extra move cost
                             find(index + 1, weightLoss, newMoveCost + 2, itemsIncludingMe);
                             occupied.markCells(me, false);
-                            me.spanX ++;
-                            me.spanY ++;
+                            me.spanX++;
+                            me.spanY++;
                         }
                         me.cellX = myX;
                         me.cellY = myY;
@@ -565,11 +573,11 @@
                     float newMoveCost = moveCost;
                     if (newX != myX) {
                         me.cellX = newX;
-                        newMoveCost ++;
+                        newMoveCost++;
                     }
                     if (newY != myY) {
                         me.cellY = newY;
-                        newMoveCost ++;
+                        newMoveCost++;
                     }
                     if (ignoreMove) {
                         newMoveCost = moveCost;
@@ -602,7 +610,7 @@
     }
 
     private ArrayList<DbEntry> loadHotseatEntries() {
-        Cursor c =  queryWorkspace(
+        Cursor c = queryWorkspace(
                 new String[]{
                         Favorites._ID,                  // 0
                         Favorites.ITEM_TYPE,            // 1
@@ -787,7 +795,7 @@
     }
 
     protected Cursor queryWorkspace(String[] columns, String where) {
-        return mDb.query(Favorites.TABLE_NAME, columns, where, null, null, null, null);
+        return mDb.query(mTableName, columns, where, null, null, null, null);
     }
 
     /**
@@ -879,24 +887,44 @@
     }
 
     /**
-     * Migrates the workspace and hotseat in case their sizes changed.
+     * Check given a new IDP, if migration is necessary.
+     */
+    public static boolean needsToMigrate(Context context, InvariantDeviceProfile idp) {
+        SharedPreferences prefs = Utilities.getPrefs(context);
+        String gridSizeString = getPointString(idp.numColumns, idp.numRows);
+
+        return !gridSizeString.equals(prefs.getString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, ""))
+                || idp.numHotseatIcons != prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT,
+                idp.numHotseatIcons);
+    }
+
+    /** See {@link #migrateGridIfNeeded(Context, InvariantDeviceProfile)} */
+    public static boolean migrateGridIfNeeded(Context context) {
+        if (context instanceof LauncherPreviewRenderer.PreviewContext) {
+            return true;
+        }
+        return migrateGridIfNeeded(context, null);
+    }
+
+    /**
+     * Run the migration algorithm if needed. For preview, we provide the intended idp because it
+     * has not been changed. If idp is null, we read it from the context, for actual grid migration.
      *
      * @return false if the migration failed.
      */
-    public static boolean migrateGridIfNeeded(Context context) {
-        SharedPreferences prefs = Utilities.getPrefs(context);
-        InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
+    public static boolean migrateGridIfNeeded(Context context, InvariantDeviceProfile idp) {
+        boolean migrateForPreview = idp != null;
+        if (!migrateForPreview) {
+            idp = LauncherAppState.getIDP(context);
+        }
 
-        String gridSizeString = getPointString(idp.numColumns, idp.numRows);
-
-        if (gridSizeString.equals(prefs.getString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, "")) &&
-                idp.numHotseatIcons == prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT,
-                        idp.numHotseatIcons)) {
-            // Skip if workspace and hotseat sizes have not changed.
+        if (!needsToMigrate(context, idp)) {
             return true;
         }
 
-        long migrationStartTime = System.currentTimeMillis();
+        SharedPreferences prefs = Utilities.getPrefs(context);
+        String gridSizeString = getPointString(idp.numColumns, idp.numRows);
+        long migrationStartTime = SystemClock.elapsedRealtime();
         try (SQLiteTransaction transaction = (SQLiteTransaction) Settings.call(
                 context.getContentResolver(), Settings.METHOD_NEW_TRANSACTION)
                 .getBinder(Settings.EXTRA_VALUE)) {
@@ -907,33 +935,39 @@
                     KEY_MIGRATION_SRC_WORKSPACE_SIZE, gridSizeString));
 
             boolean dbChanged = false;
+            if (migrateForPreview) {
+                copyTable(transaction.getDb(), Favorites.TABLE_NAME, Favorites.PREVIEW_TABLE_NAME,
+                        context);
+            }
 
             GridBackupTable backupTable = new GridBackupTable(context, transaction.getDb(),
                     srcHotseatCount, sourceSize.x, sourceSize.y);
-            if (backupTable.backupOrRestoreAsNeeded()) {
+            if (migrateForPreview ? backupTable.restoreToPreviewIfBackupExists()
+                    : backupTable.backupOrRestoreAsNeeded()) {
                 dbChanged = true;
                 srcHotseatCount = backupTable.getRestoreHotseatAndGridSize(sourceSize);
             }
 
             HashSet<String> validPackages = getValidPackages(context);
-            // Hotseat
+            // Hotseat.
             if (srcHotseatCount != idp.numHotseatIcons) {
                 // Migrate hotseat.
-                dbChanged = new GridSizeMigrationTask(context, transaction.getDb(),
-                        validPackages, srcHotseatCount, idp.numHotseatIcons).migrateHotseat();
+                dbChanged = new GridSizeMigrationTask(context, transaction.getDb(), validPackages,
+                        migrateForPreview, srcHotseatCount, idp.numHotseatIcons).migrateHotseat();
             }
 
             // Grid size
             Point targetSize = new Point(idp.numColumns, idp.numRows);
-            if (new MultiStepMigrationTask(validPackages, context, transaction.getDb())
-                    .migrate(sourceSize, targetSize)) {
+            if (new MultiStepMigrationTask(validPackages, context, transaction.getDb(),
+                    migrateForPreview).migrate(sourceSize, targetSize)) {
                 dbChanged = true;
             }
 
             if (dbChanged) {
                 // Make sure we haven't removed everything.
                 final Cursor c = context.getContentResolver().query(
-                        Favorites.CONTENT_URI, null, null, null, null);
+                        migrateForPreview ? Favorites.PREVIEW_CONTENT_URI : Favorites.CONTENT_URI,
+                        null, null, null, null);
                 boolean hasData = c.moveToNext();
                 c.close();
                 if (!hasData) {
@@ -942,21 +976,25 @@
             }
 
             transaction.commit();
-            Settings.call(context.getContentResolver(), Settings.METHOD_REFRESH_BACKUP_TABLE);
+            if (!migrateForPreview) {
+                Settings.call(context.getContentResolver(), Settings.METHOD_REFRESH_BACKUP_TABLE);
+            }
             return true;
         } catch (Exception e) {
-            Log.e(TAG, "Error during grid migration", e);
+            Log.e(TAG, "Error during preview grid migration", e);
 
             return false;
         } finally {
-            Log.v(TAG, "Workspace migration completed in "
-                    + (System.currentTimeMillis() - migrationStartTime));
+            Log.v(TAG, "Preview workspace migration completed in "
+                    + (SystemClock.elapsedRealtime() - migrationStartTime));
 
-            // Save current configuration, so that the migration does not run again.
-            prefs.edit()
-                    .putString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, gridSizeString)
-                    .putInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, idp.numHotseatIcons)
-                    .apply();
+            if (!migrateForPreview) {
+                // Save current configuration, so that the migration does not run again.
+                prefs.edit()
+                        .putString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, gridSizeString)
+                        .putInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, idp.numHotseatIcons)
+                        .apply();
+            }
         }
     }
 
@@ -989,7 +1027,7 @@
                 .getBinder(Settings.EXTRA_VALUE)) {
             GridSizeMigrationTask task = new GridSizeMigrationTask(
                     context, transaction.getDb(), getValidPackages(context),
-                    Integer.MAX_VALUE, Integer.MAX_VALUE);
+                    false /* usePreviewTable */, Integer.MAX_VALUE, Integer.MAX_VALUE);
 
             // Load all the valid entries
             ArrayList<DbEntry> items = task.loadHotseatEntries();
@@ -1011,12 +1049,14 @@
         private final HashSet<String> mValidPackages;
         private final Context mContext;
         private final SQLiteDatabase mDb;
+        private final boolean mUsePreviewTable;
 
         public MultiStepMigrationTask(HashSet<String> validPackages, Context context,
-                SQLiteDatabase db) {
+                SQLiteDatabase db, boolean usePreviewTable) {
             mValidPackages = validPackages;
             mContext = context;
             mDb = db;
+            mUsePreviewTable = usePreviewTable;
         }
 
         public boolean migrate(Point sourceSize, Point targetSize) throws Exception {
@@ -1052,8 +1092,8 @@
         }
 
         protected boolean runStepTask(Point sourceSize, Point nextSize) throws Exception {
-            return new GridSizeMigrationTask(mContext, mDb,
-                    mValidPackages, sourceSize, nextSize).migrateWorkspace();
+            return new GridSizeMigrationTask(mContext, mDb, mValidPackages, mUsePreviewTable,
+                    sourceSize, nextSize).migrateWorkspace();
         }
     }
 }
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
index 63b7191..197b29c 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
@@ -18,6 +18,8 @@
 
 import android.content.Context;
 
+import com.android.launcher3.InvariantDeviceProfile;
+
 /**
  * This class takes care of shrinking the workspace (by maximum of one row and one column), as a
  * result of restoring from a larger device or device density change.
@@ -28,12 +30,19 @@
 
     }
 
+    /** See {@link #migrateGridIfNeeded(Context, InvariantDeviceProfile)} */
+    public static boolean migrateGridIfNeeded(Context context) {
+        // To be implemented.
+        return true;
+    }
+
     /**
-     * Migrates the workspace and hotseat in case their sizes changed.
+     * Run the migration algorithm if needed. For preview, we provide the intended idp because it
+     * has not been changed. If idp is null, we read it from the context, for actual grid migration.
      *
      * @return false if the migration failed.
      */
-    public static boolean migrateGridIfNeeded(Context context) {
+    public static boolean migrateGridIfNeeded(Context context, InvariantDeviceProfile idp) {
         // To be implemented.
         return true;
     }
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 4961432..2311dcc 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -28,6 +28,7 @@
 import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.database.CursorWrapper;
+import android.net.Uri;
 import android.os.UserHandle;
 import android.provider.BaseColumns;
 import android.text.TextUtils;
@@ -64,6 +65,7 @@
 
     public final LongSparseArray<UserHandle> allUsers = new LongSparseArray<>();
 
+    private final Uri mContentUri;
     private final Context mContext;
     private final PackageManager mPM;
     private final IconCache mIconCache;
@@ -96,8 +98,10 @@
     public int itemType;
     public int restoreFlag;
 
-    public LoaderCursor(Cursor c, LauncherAppState app) {
-        super(c);
+    public LoaderCursor(Cursor cursor, Uri contentUri, LauncherAppState app) {
+        super(cursor);
+
+        mContentUri = contentUri;
         mContext = app.getContext();
         mIconCache = app.getIconCache();
         mIDP = app.getInvariantDeviceProfile();
@@ -312,9 +316,8 @@
     public boolean commitDeleted() {
         if (itemsToRemove.size() > 0) {
             // Remove dead items
-            mContext.getContentResolver().delete(LauncherSettings.Favorites.CONTENT_URI,
-                    Utilities.createDbSelectionQuery(
-                            LauncherSettings.Favorites._ID, itemsToRemove), null);
+            mContext.getContentResolver().delete(mContentUri, Utilities.createDbSelectionQuery(
+                    LauncherSettings.Favorites._ID, itemsToRemove), null);
             return true;
         }
         return false;
@@ -339,7 +342,7 @@
             // Update restored items that no longer require special handling
             ContentValues values = new ContentValues();
             values.put(LauncherSettings.Favorites.RESTORED, 0);
-            mContext.getContentResolver().update(LauncherSettings.Favorites.CONTENT_URI, values,
+            mContext.getContentResolver().update(mContentUri, values,
                     Utilities.createDbSelectionQuery(
                             LauncherSettings.Favorites._ID, restoredRows), null);
         }
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 6223a23..fbf01fc 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -36,6 +36,7 @@
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.content.pm.ShortcutInfo;
+import android.net.Uri;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.text.TextUtils;
@@ -76,7 +77,6 @@
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.shortcuts.ShortcutRequest;
 import com.android.launcher3.shortcuts.ShortcutRequest.QueryResult;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.IOUtils;
 import com.android.launcher3.util.LooperIdleLock;
@@ -106,7 +106,7 @@
 
     private final LauncherAppState mApp;
     private final AllAppsList mBgAllAppsList;
-    private final BgDataModel mBgDataModel;
+    protected final BgDataModel mBgDataModel;
 
     private FirstScreenBroadcast mFirstScreenBroadcast;
 
@@ -284,6 +284,10 @@
 
     @VisibleForTesting
     void loadWorkspace(List<ShortcutInfo> allDeepShortcuts) {
+        loadWorkspace(allDeepShortcuts, LauncherSettings.Favorites.CONTENT_URI);
+    }
+
+    protected void loadWorkspace(List<ShortcutInfo> allDeepShortcuts, Uri contentUri) {
         final Context context = mApp.getContext();
         final ContentResolver contentResolver = context.getContentResolver();
         final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
@@ -327,8 +331,8 @@
             mFirstScreenBroadcast = new FirstScreenBroadcast(installingPkgs);
 
             Map<ShortcutKey, ShortcutInfo> shortcutKeyToPinnedShortcuts = new HashMap<>();
-            final LoaderCursor c = new LoaderCursor(contentResolver.query(
-                    LauncherSettings.Favorites.CONTENT_URI, null, null, null, null), mApp);
+            final LoaderCursor c = new LoaderCursor(
+                    contentResolver.query(contentUri, null, null, null, null), contentUri, mApp);
 
             Map<ComponentKey, AppWidgetProviderInfo> widgetProvidersMap = null;
 
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index d9bd714..1c2acfd 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -32,6 +32,7 @@
 import android.graphics.Rect;
 import android.graphics.drawable.ShapeDrawable;
 import android.util.AttributeSet;
+import android.util.Pair;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -358,6 +359,11 @@
         }
     }
 
+    @Override
+    protected Pair<View, String> getAccessibilityTarget() {
+        return Pair.create(this, "");
+    }
+
     private void animateOpen() {
         setVisibility(View.VISIBLE);
 
@@ -413,6 +419,8 @@
         }
         if (getOutlineProvider() instanceof RevealOutlineAnimation) {
             ((RevealOutlineAnimation) getOutlineProvider()).getOutline(mEndRect);
+        } else {
+            mEndRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
         }
         if (mOpenCloseAnimator != null) {
             mOpenCloseAnimator.cancel();
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 05ea694..445acca 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -37,7 +37,6 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.util.AttributeSet;
-import android.util.Pair;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
@@ -330,11 +329,6 @@
     }
 
     @Override
-    protected Pair<View, String> getAccessibilityTarget() {
-        return Pair.create(this, "");
-    }
-
-    @Override
     protected void getTargetObjectLocation(Rect outPos) {
         getPopupContainer().getDescendantRectRelativeToSelf(mOriginalIcon, outPos);
         outPos.top += mOriginalIcon.getPaddingTop();
diff --git a/src/com/android/launcher3/provider/LauncherDbUtils.java b/src/com/android/launcher3/provider/LauncherDbUtils.java
index 2c843f9..f7ecc3f 100644
--- a/src/com/android/launcher3/provider/LauncherDbUtils.java
+++ b/src/com/android/launcher3/provider/LauncherDbUtils.java
@@ -22,10 +22,12 @@
 import android.database.DatabaseUtils;
 import android.database.sqlite.SQLiteDatabase;
 import android.os.Binder;
+import android.os.Process;
 import android.util.Log;
 
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.util.IntArray;
 
 import java.util.Locale;
@@ -116,6 +118,15 @@
         db.execSQL("DROP TABLE IF EXISTS " + tableName);
     }
 
+    /** Copy from table to the to table. */
+    public static void copyTable(SQLiteDatabase db, String from, String to, Context context) {
+        long userSerial = UserCache.INSTANCE.get(context).getSerialNumberForUser(
+                Process.myUserHandle());
+        dropTable(db, to);
+        Favorites.addTableToDb(db, userSerial, false, to);
+        db.execSQL("INSERT INTO " + to + " SELECT * FROM " + from);
+    }
+
     /**
      * Utility class to simplify managing sqlite transactions
      */
diff --git a/src/com/android/launcher3/provider/LossyScreenMigrationTask.java b/src/com/android/launcher3/provider/LossyScreenMigrationTask.java
index 6d839f3..c0111b9 100644
--- a/src/com/android/launcher3/provider/LossyScreenMigrationTask.java
+++ b/src/com/android/launcher3/provider/LossyScreenMigrationTask.java
@@ -18,7 +18,6 @@
 
 import android.content.ContentValues;
 import android.content.Context;
-import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.graphics.Point;
 
@@ -43,7 +42,7 @@
     protected LossyScreenMigrationTask(
             Context context, InvariantDeviceProfile idp, SQLiteDatabase db) {
         // Decrease the rows count by 1
-        super(context, db, getValidPackages(context),
+        super(context, db, getValidPackages(context), false /* usePreviewTable */,
                 new Point(idp.numColumns, idp.numRows + 1),
                 new Point(idp.numColumns, idp.numRows));
 
diff --git a/src/com/android/launcher3/util/MainThreadInitializedObject.java b/src/com/android/launcher3/util/MainThreadInitializedObject.java
index 520a9ed..fc9f8f7 100644
--- a/src/com/android/launcher3/util/MainThreadInitializedObject.java
+++ b/src/com/android/launcher3/util/MainThreadInitializedObject.java
@@ -22,6 +22,7 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.launcher3.graphics.LauncherPreviewRenderer.PreviewContext;
 import com.android.launcher3.util.ResourceBasedOverride.Overrides;
 
 import java.util.concurrent.ExecutionException;
@@ -39,6 +40,10 @@
     }
 
     public T get(Context context) {
+        if (context instanceof PreviewContext) {
+            return ((PreviewContext) context).getObject(this, mProvider);
+        }
+
         if (mValue == null) {
             if (Looper.myLooper() == Looper.getMainLooper()) {
                 mValue = TraceHelper.whitelistIpcs("main.thread.object",
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 4ffc251..5e98184 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -22,10 +22,9 @@
 import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
-import static java.lang.System.exit;
-
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -38,7 +37,6 @@
 import android.content.pm.PackageManager;
 import android.os.Process;
 import android.os.RemoteException;
-import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.uiautomator.By;
@@ -102,6 +100,7 @@
     protected final LauncherInstrumentation mLauncher = new LauncherInstrumentation();
     protected Context mTargetContext;
     protected String mTargetPackage;
+    private int mLauncherPid;
 
     protected AbstractLauncherUiTest() {
         mLauncher.enableCheckEventsForSuccessfulGestures();
@@ -158,7 +157,7 @@
 
         return TestHelpers.isInLauncherProcess()
                 ? RuleChain.outerRule(ShellCommandRule.setDefaultLauncher())
-                        .around(inner) :
+                .around(inner) :
                 inner;
     }
 
@@ -175,17 +174,22 @@
 
     @Before
     public void setUp() throws Exception {
+        mLauncherPid = 0;
         // Disable app tracker
         AppLaunchTracker.INSTANCE.initializeForTesting(new AppLaunchTracker());
 
         mTargetContext = InstrumentationRegistry.getTargetContext();
         mTargetPackage = mTargetContext.getPackageName();
+        mLauncherPid = mLauncher.getPid();
     }
 
     @After
     public void verifyLauncherState() {
         // Limits UI tests affecting tests running after them.
         mLauncher.waitForLauncherInitialized();
+        if (mLauncherPid != 0) {
+            assertEquals("Launcher crashed, pid mismatch:", mLauncherPid, mLauncher.getPid());
+        }
     }
 
     protected void clearLauncherData() throws IOException, InterruptedException {
@@ -196,6 +200,7 @@
         } else {
             clearPackageData(mDevice.getLauncherPackageName());
             mLauncher.enableDebugTracing();
+            mLauncherPid = mLauncher.getPid();
         }
     }
 
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 54caf1e..7475efe 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -40,7 +40,6 @@
 import com.android.launcher3.widget.WidgetsFullSheet;
 import com.android.launcher3.widget.WidgetsRecyclerView;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
@@ -51,21 +50,10 @@
 public class TaplTestsLauncher3 extends AbstractLauncherUiTest {
     private static final String APP_NAME = "LauncherTestApp";
 
-    private int mLauncherPid;
-
     @Before
     public void setUp() throws Exception {
-        mLauncherPid = 0;
         super.setUp();
         initialize(this);
-        mLauncherPid = mLauncher.getPid();
-    }
-
-    @After
-    public void teardown() {
-        if (mLauncherPid != 0) {
-            assertEquals("Launcher crashed, pid mismatch:", mLauncherPid, mLauncher.getPid());
-        }
     }
 
     public static void initialize(AbstractLauncherUiTest test) throws Exception {
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index b3b887d..6775521 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -145,7 +145,7 @@
 
     private static final String WORKSPACE_RES_ID = "workspace";
     private static final String APPS_RES_ID = "apps_view";
-    private static final String OVERVIEW_RES_ID = "overview_panel";
+    private static final String OVERVIEW_RES_ID = "overview_panel_recents";
     private static final String WIDGETS_RES_ID = "widgets_list_view";
     private static final String CONTEXT_MENU_RES_ID = "deep_shortcuts_container";
     public static final int WAIT_TIME_MS = 10000;