Merge "Rotate Overview Task Action Menu" into ub-launcher3-rvc-dev
diff --git a/quickstep/recents_ui_overrides/res/values/dimens.xml b/quickstep/recents_ui_overrides/res/values/dimens.xml
index 363840a..9266b06 100644
--- a/quickstep/recents_ui_overrides/res/values/dimens.xml
+++ b/quickstep/recents_ui_overrides/res/values/dimens.xml
@@ -20,6 +20,7 @@
     <dimen name="chip_hint_start_padding">10dp</dimen>
     <dimen name="chip_hint_end_padding">12dp</dimen>
     <dimen name="chip_hint_horizontal_margin">20dp</dimen>
+    <dimen name="chip_hint_vertical_offset">16dp</dimen>
     <dimen name="chip_hint_elevation">2dp</dimen>
     <dimen name="chip_icon_size">16dp</dimen>
     <dimen name="chip_text_height">26dp</dimen>
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index 8cf5da2..79b4002 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -197,14 +197,15 @@
             case INDEX_RECENTS_FADE_ANIM:
                 return ObjectAnimator.ofFloat(mLauncher.getOverviewPanel(),
                         RecentsView.CONTENT_ALPHA, values);
-            case INDEX_RECENTS_TRANSLATE_X_ANIM:
-                // TODO: Do not assume motion across X axis for adjacent page
+            case INDEX_RECENTS_TRANSLATE_X_ANIM: {
+                RecentsView rv = mLauncher.getOverviewPanel();
                 return new SpringAnimationBuilder(mLauncher)
-                        .setMinimumVisibleChange(1f / mLauncher.getOverviewPanel().getWidth())
+                        .setMinimumVisibleChange(1f / rv.getPageOffsetScale())
                         .setDampingRatio(0.8f)
                         .setStiffness(250)
                         .setValues(values)
-                        .build(mLauncher.getOverviewPanel(), ADJACENT_PAGE_OFFSET);
+                        .build(rv, ADJACENT_PAGE_OFFSET);
+            }
             case INDEX_PAUSE_TO_OVERVIEW_ANIM: {
                 StateAnimationConfig config = new StateAnimationConfig();
                 config.duration = ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW;
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index 77118d5..2b456ec 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -15,7 +15,6 @@
  */
 package com.android.launcher3.uioverrides.touchcontrollers;
 
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
@@ -23,6 +22,7 @@
 import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.touch.AbstractStateChangeTouchController.SUCCESS_TRANSITION_PROGRESS;
+import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 
 import android.animation.ValueAnimator;
@@ -131,12 +131,8 @@
         final PendingAnimation builder = new PendingAnimation(accuracy);
         if (mStartState == OVERVIEW) {
             RecentsView recentsView = mLauncher.getOverviewPanel();
-            float pullbackDist = mPullbackDistance;
-            if (!recentsView.isRtl()) {
-                pullbackDist = -pullbackDist;
-            }
-
-            builder.setFloat(recentsView, VIEW_TRANSLATE_X, pullbackDist, PULLBACK_INTERPOLATOR);
+            builder.setFloat(recentsView, ADJACENT_PAGE_OFFSET,
+                    -mPullbackDistance / recentsView.getPageOffsetScale(), PULLBACK_INTERPOLATOR);
             if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
                 builder.addOnFrameCallback(
                         () -> recentsView.redrawLiveTile(false /* mightNeedToRefill */));
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index 71aa2e8..381ecf1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -166,8 +166,8 @@
         float velocityDp = dpiFromPx(velocity);
         boolean isFling = Math.abs(velocityDp) > 1;
         LauncherStateManager stateManager = mLauncher.getStateManager();
-        if (isFling) {
-            // When flinging, go back to home instead of overview.
+        boolean goToHomeInsteadOfOverview = isFling;
+        if (goToHomeInsteadOfOverview) {
             if (velocity > 0) {
                 stateManager.goToState(NORMAL, true,
                         () -> onSwipeInteractionCompleted(NORMAL, Touch.FLING));
@@ -187,20 +187,21 @@
                         () -> onSwipeInteractionCompleted(NORMAL, Touch.SWIPE)));
                 anim.start();
             }
-        } else {
-            if (mReachedOverview) {
-                float distanceDp = dpiFromPx(Math.max(
-                        Math.abs(mRecentsView.getTranslationX()),
-                        Math.abs(mRecentsView.getTranslationY())));
-                long duration = (long) Math.max(TRANSLATION_ANIM_MIN_DURATION_MS,
-                        distanceDp / TRANSLATION_ANIM_VELOCITY_DP_PER_MS);
-                mRecentsView.animate()
-                        .translationX(0)
-                        .translationY(0)
-                        .setInterpolator(ACCEL_DEACCEL)
-                        .setDuration(duration)
-                        .withEndAction(this::maybeSwipeInteractionToOverviewComplete);
-            }
+        }
+        if (mReachedOverview) {
+            float distanceDp = dpiFromPx(Math.max(
+                    Math.abs(mRecentsView.getTranslationX()),
+                    Math.abs(mRecentsView.getTranslationY())));
+            long duration = (long) Math.max(TRANSLATION_ANIM_MIN_DURATION_MS,
+                    distanceDp / TRANSLATION_ANIM_VELOCITY_DP_PER_MS);
+            mRecentsView.animate()
+                    .translationX(0)
+                    .translationY(0)
+                    .setInterpolator(ACCEL_DEACCEL)
+                    .setDuration(duration)
+                    .withEndAction(goToHomeInsteadOfOverview
+                            ? null
+                            : this::maybeSwipeInteractionToOverviewComplete);
         }
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
index bf01780..52b40a9 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
@@ -1077,7 +1077,8 @@
     }
 
     private void continueComputingRecentsScrollIfNecessary() {
-        if (!mGestureState.hasState(STATE_RECENTS_SCROLLING_FINISHED)) {
+        if (!mGestureState.hasState(STATE_RECENTS_SCROLLING_FINISHED)
+                && !mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
             computeRecentsScrollIfInvisible();
             mRecentsView.post(this::continueComputingRecentsScrollIfNecessary);
         }
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 9309110..5abbd86 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
@@ -208,7 +208,6 @@
             float alpha;
             float cornerRadius = 0f;
             float scale = Math.max(mCurrentRect.width(), mTargetRect.width()) / crop.width();
-            int layer = RemoteAnimationProvider.getLayer(app, mBoostModeTargetLayers);
             if (app.mode == params.mTargetSet.targetMode) {
                 alpha = mTaskAlphaCallback.getAlpha(app, params.mTargetAlpha);
                 if (app.activityType != RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
@@ -245,13 +244,11 @@
                 alpha = mBaseAlphaCallback.getAlpha(app, progress);
                 if (ENABLE_QUICKSTEP_LIVE_TILE.get() && params.mLauncherOnTop) {
                     crop = null;
-                    layer = Integer.MAX_VALUE;
                 }
             }
             builder.withAlpha(alpha)
                     .withMatrix(mTmpMatrix)
                     .withWindowCrop(crop)
-                    .withLayer(layer)
                     // Since radius is in Surface space, but we draw the rounded corners in screen
                     // space, we have to undo the scale
                     .withCornerRadius(cornerRadius / scale);
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 90dc5a4..3b6fd13 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
@@ -1670,6 +1670,13 @@
         updateCurveProperties();
     }
 
+    /**
+     * TODO: Do not assume motion across X axis for adjacent page
+     */
+    public float getPageOffsetScale() {
+        return Math.max(getWidth(), 1);
+    }
+
     private void updateDeadZoneRects() {
         // Get the deadzone rect surrounding the clear all button to not dismiss overview to home
         mClearAllButtonDeadZoneRect.setEmpty();
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 ce8a193..3cd2b32 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
@@ -174,11 +174,12 @@
 
     // Order in which the footers appear. Lower order appear below higher order.
     public static final int INDEX_DIGITAL_WELLBEING_TOAST = 0;
-    public static final int INDEX_PROACTIVE_SUGGEST = 1;
     private final FooterWrapper[] mFooters = new FooterWrapper[2];
     private float mFooterVerticalOffset = 0;
     private float mFooterAlpha = 1;
     private int mStackHeight;
+    private View mContextualChipWrapper;
+    private View mContextualChip;
 
     public TaskView(Context context) {
         this(context, null);
@@ -255,8 +256,14 @@
                     footer.animateHide();
                 }
             }
+            if (mContextualChip != null) {
+                mContextualChip.animate().scaleX(0f).scaleY(0f).setDuration(300);
+            }
             mIconView.animate().alpha(0.0f);
         } else {
+            if (mContextualChip != null) {
+                mContextualChip.animate().scaleX(1f).scaleY(1f).setDuration(300);
+            }
             mIconView.animate().alpha(1.0f);
         }
 
@@ -666,6 +673,51 @@
         return oldFooter;
     }
 
+    /**
+     * Sets the contextual chip.
+     *
+     * @param view Wrapper view containing contextual chip.
+     */
+    public void setContextualChip(View view) {
+        if (mContextualChipWrapper != null) {
+            removeView(mContextualChipWrapper);
+        }
+        if (view != null) {
+            mContextualChipWrapper = view;
+            LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT,
+                    LayoutParams.WRAP_CONTENT);
+            layoutParams.gravity = BOTTOM | CENTER_HORIZONTAL;
+            layoutParams.bottomMargin = (int)
+                    (((MarginLayoutParams) mSnapshotView.getLayoutParams()).bottomMargin
+                            - getExpectedViewHeight(view) + getResources().getDimension(
+                            R.dimen.chip_hint_vertical_offset));
+            mContextualChip = ((FrameLayout) mContextualChipWrapper).getChildAt(0);
+            mContextualChip.setScaleX(0f);
+            mContextualChip.setScaleY(0f);
+            addView(view, getChildCount(), layoutParams);
+            view.setAlpha(mFooterAlpha);
+            if (mContextualChip != null) {
+                mContextualChip.animate().scaleX(1f).scaleY(1f).setDuration(50);
+            }
+        }
+
+    }
+
+    /**
+     * Clears the contextual chip from TaskView.
+     *
+     * @return The contextual chip wrapper view to be recycled.
+     */
+    public View clearContextualChip() {
+        if (mContextualChipWrapper != null) {
+            removeView(mContextualChipWrapper);
+        }
+        View oldContextualChipWrapper = mContextualChipWrapper;
+        mContextualChipWrapper = null;
+        mContextualChip = null;
+        return oldContextualChipWrapper;
+    }
+
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
@@ -759,14 +811,7 @@
             mDelegate = mOldOutlineProvider == null
                     ? ViewOutlineProvider.BACKGROUND : mOldOutlineProvider;
 
-            int h = view.getLayoutParams().height;
-            if (h > 0) {
-                mExpectedHeight = h;
-            } else {
-                int m = MeasureSpec.makeMeasureSpec(MeasureSpec.EXACTLY - 1, MeasureSpec.AT_MOST);
-                view.measure(m, m);
-                mExpectedHeight = view.getMeasuredHeight();
-            }
+            mExpectedHeight = getExpectedViewHeight(view);
             mOldPaddingBottom = view.getPaddingBottom();
 
             if (mOldOutlineProvider != null) {
@@ -828,6 +873,19 @@
         }
     }
 
+    private int getExpectedViewHeight(View view) {
+        int expectedHeight;
+        int h = view.getLayoutParams().height;
+        if (h > 0) {
+            expectedHeight = h;
+        } else {
+            int m = MeasureSpec.makeMeasureSpec(MeasureSpec.EXACTLY - 1, MeasureSpec.AT_MOST);
+            view.measure(m, m);
+            expectedHeight = view.getMeasuredHeight();
+        }
+        return expectedHeight;
+    }
+
     @Override
     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfo(info);
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 65763d4..af63a25 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -161,6 +161,8 @@
     @Override
     protected void setupViews() {
         super.setupViews();
+
+        SysUINavigationMode.INSTANCE.get(this).updateMode();
         mActionsView = findViewById(R.id.overview_actions_view);
         ((RecentsView) getOverviewPanel()).init(mActionsView);
 
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index 70cbd82..1cb0aa4 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -611,7 +611,6 @@
                                 .withWindowCrop(target.screenSpaceBounds)
                                 .withAlpha(1f);
                     }
-                    builder.withLayer(RemoteAnimationProvider.getLayer(target, MODE_OPENING));
                     params[i] = builder.build();
                 }
                 surfaceApplier.scheduleApply(params);
@@ -718,7 +717,6 @@
                     params[i] = new SurfaceParams.Builder(target.leash)
                             .withAlpha(1f)
                             .withWindowCrop(target.screenSpaceBounds)
-                            .withLayer(RemoteAnimationProvider.getLayer(target, MODE_OPENING))
                             .withCornerRadius(cornerRadius)
                             .build();
                 }
@@ -775,7 +773,6 @@
                     }
                     params[i] = builder
                             .withWindowCrop(target.screenSpaceBounds)
-                            .withLayer(RemoteAnimationProvider.getLayer(target, MODE_CLOSING))
                             .build();
                 }
                 surfaceApplier.scheduleApply(params);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index cc3fd97..e5c9fc9 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -22,10 +22,12 @@
 import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
 import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
+import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 
 import android.animation.TimeInterpolator;
@@ -132,7 +134,12 @@
             return TouchInteractionService.isConnected() ?
                     mLauncher.getStateManager().getLastState() : NORMAL;
         } else if (fromState == OVERVIEW) {
-            return isDragTowardPositive ? ALL_APPS : NORMAL;
+            LauncherState positiveDragTarget = ALL_APPS;
+            if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(mLauncher)) {
+                // Don't allow swiping up to all apps.
+                positiveDragTarget = OVERVIEW;
+            }
+            return isDragTowardPositive ? positiveDragTarget : NORMAL;
         } else if (fromState == NORMAL && isDragTowardPositive) {
             int stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags();
             return mAllowDragToOverview && TouchInteractionService.isConnected()
diff --git a/quickstep/src/com/android/quickstep/SysUINavigationMode.java b/quickstep/src/com/android/quickstep/SysUINavigationMode.java
index 6a10b37..c715c93 100644
--- a/quickstep/src/com/android/quickstep/SysUINavigationMode.java
+++ b/quickstep/src/com/android/quickstep/SysUINavigationMode.java
@@ -74,15 +74,20 @@
         mContext.registerReceiver(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
-                Mode oldMode = mMode;
-                initializeMode();
-                if (mMode != oldMode) {
-                    dispatchModeChange();
-                }
+                updateMode();
             }
         }, getPackageFilter("android", ACTION_OVERLAY_CHANGED));
     }
 
+    /** Updates navigation mode when needed. */
+    public void updateMode() {
+        Mode oldMode = mMode;
+        initializeMode();
+        if (mMode != oldMode) {
+            dispatchModeChange();
+        }
+    }
+
     private void initializeMode() {
         int modeInt = getSystemIntegerRes(mContext, NAV_BAR_INTERACTION_MODE_RES_NAME);
         for(Mode m : Mode.values()) {
diff --git a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
index 21b97ec..4cd0206 100644
--- a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
+++ b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
@@ -62,15 +62,12 @@
     static void prepareTargetsForFirstFrame(RemoteAnimationTargetCompat[] targets,
             TransactionCompat t, int boostModeTargets) {
         for (RemoteAnimationTargetCompat target : targets) {
-            t.setLayer(target.leash, getLayer(target, boostModeTargets));
             t.show(target.leash);
         }
     }
 
     public static int getLayer(RemoteAnimationTargetCompat target, int boostModeTarget) {
-        return target.mode == boostModeTarget
-                ? Z_BOOST_BASE + target.prefixOrderIndex
-                : target.prefixOrderIndex;
+        return target.prefixOrderIndex;
     }
 
     /**
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index 0afe4a8..40265c4 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -203,7 +203,7 @@
                         + launcher.getNavigationModeMismatchError(),
                 () -> launcher.getNavigationModeMismatchError() == null,
                 60000 /* b/148422894 */, launcher);
-        AbstractLauncherUiTest.checkDetectedLeaks();
+        AbstractLauncherUiTest.checkDetectedLeaks(launcher);
         return true;
     }
 
diff --git a/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java b/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java
index b7340cf..bbbe21e 100644
--- a/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java
@@ -130,7 +130,7 @@
         }
         helper.close();
 
-        helper = new DatabaseHelper(mContext, DB_FILE) {
+        helper = new DatabaseHelper(mContext, DB_FILE, false) {
             @Override
             public void onOpen(SQLiteDatabase db) { }
         };
@@ -161,7 +161,7 @@
 
         DbDowngradeHelper.updateSchemaFile(mSchemaFile, LauncherProvider.SCHEMA_VERSION, mContext);
 
-        DatabaseHelper dbHelper = new DatabaseHelper(mContext, DB_FILE) {
+        DatabaseHelper dbHelper = new DatabaseHelper(mContext, DB_FILE, false) {
             @Override
             public void onOpen(SQLiteDatabase db) { }
         };
diff --git a/robolectric_tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java b/robolectric_tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
index 58174c7..ee73b82 100644
--- a/robolectric_tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
+++ b/robolectric_tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
@@ -95,7 +95,7 @@
         private final long mProfileId;
 
         MyDatabaseHelper(long profileId) {
-            super(RuntimeEnvironment.application, null);
+            super(RuntimeEnvironment.application, null, false);
             mProfileId = profileId;
         }
 
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index e8e88c4..d7d4a27 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -79,8 +79,10 @@
 
     private static final int[] STATE_PRESSED = new int[] {android.R.attr.state_pressed};
 
-    private final PointF mTranslationForReorder = new PointF(0, 0);
-    private float mScaleForReorder = 1f;
+    private final PointF mTranslationForReorderBounce = new PointF(0, 0);
+    private final PointF mTranslationForReorderPreview = new PointF(0, 0);
+
+    private float mScaleForReorderBounce = 1f;
 
     private static final Property<BubbleTextView, Float> DOT_SCALE_PROPERTY
             = new Property<BubbleTextView, Float>(Float.TYPE, "dotScale") {
@@ -675,24 +677,39 @@
         return mIconSize;
     }
 
-    public void setReorderOffset(float x, float y) {
-        mTranslationForReorder.set(x, y);
-        super.setTranslationX(x);
-        super.setTranslationY(y);
+    private void updateTranslation() {
+        super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x);
+        super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y);
     }
 
-    public void getReorderOffset(PointF offset) {
-        offset.set(mTranslationForReorder);
+    public void setReorderBounceOffset(float x, float y) {
+        mTranslationForReorderBounce.set(x, y);
+        updateTranslation();
     }
 
-    public void setReorderScale(float scale) {
-        mScaleForReorder = scale;
+    public void getReorderBounceOffset(PointF offset) {
+        offset.set(mTranslationForReorderBounce);
+    }
+
+    @Override
+    public void setReorderPreviewOffset(float x, float y) {
+        mTranslationForReorderPreview.set(x, y);
+        updateTranslation();
+    }
+
+    @Override
+    public void getReorderPreviewOffset(PointF offset) {
+        offset.set(mTranslationForReorderPreview);
+    }
+
+    public void setReorderBounceScale(float scale) {
+        mScaleForReorderBounce = scale;
         super.setScaleX(scale);
         super.setScaleY(scale);
     }
 
-    public float getReorderScale() {
-        return mScaleForReorder;
+    public float getReorderBounceScale() {
+        return mScaleForReorderBounce;
     }
 
     public View getView() {
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 71a787f..ed71ddc 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -852,9 +852,10 @@
             int delay, boolean permanent, boolean adjustOccupied) {
         ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
 
-        if (clc.indexOfChild(child) != -1) {
+        if (clc.indexOfChild(child) != -1 && (child instanceof Reorderable)) {
             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
             final ItemInfo info = (ItemInfo) child.getTag();
+            final Reorderable item = (Reorderable) child;
 
             // We cancel any existing animations
             if (mReorderAnimators.containsKey(lp)) {
@@ -862,13 +863,18 @@
                 mReorderAnimators.remove(lp);
             }
 
-            final int oldX = lp.x;
-            final int oldY = lp.y;
+
             if (adjustOccupied) {
                 GridOccupancy occupied = permanent ? mOccupied : mTmpOccupied;
                 occupied.markCells(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, false);
                 occupied.markCells(cellX, cellY, lp.cellHSpan, lp.cellVSpan, true);
             }
+
+            // Compute the new x and y position based on the new cellX and cellY
+            // We leverage the actual layout logic in the layout params and hence need to modify
+            // state and revert that state.
+            final int oldX = lp.x;
+            final int oldY = lp.y;
             lp.isLockedToGrid = true;
             if (permanent) {
                 lp.cellX = info.cellX = cellX;
@@ -878,15 +884,23 @@
                 lp.tmpCellY = cellY;
             }
             clc.setupLp(child);
-            lp.isLockedToGrid = false;
             final int newX = lp.x;
             final int newY = lp.y;
-
             lp.x = oldX;
             lp.y = oldY;
+            lp.isLockedToGrid = false;
+            // End compute new x and y
+
+            item.getReorderPreviewOffset(mTmpPointF);
+            final float initPreviewOffsetX = mTmpPointF.x;
+            final float initPreviewOffsetY = mTmpPointF.y;
+            final float finalPreviewOffsetX = newX - oldX;
+            final float finalPreviewOffsetY = newY - oldY;
+
 
             // Exit early if we're not actually moving the view
-            if (oldX == newX && oldY == newY) {
+            if (finalPreviewOffsetX == 0 && finalPreviewOffsetY == 0
+                    && initPreviewOffsetX == 0 && initPreviewOffsetY == 0) {
                 lp.isLockedToGrid = true;
                 return true;
             }
@@ -899,9 +913,9 @@
                 @Override
                 public void onAnimationUpdate(ValueAnimator animation) {
                     float r = (Float) animation.getAnimatedValue();
-                    lp.x = (int) ((1 - r) * oldX + r * newX);
-                    lp.y = (int) ((1 - r) * oldY + r * newY);
-                    child.requestLayout();
+                    float x = (1 - r) * initPreviewOffsetX + r * finalPreviewOffsetX;
+                    float y = (1 - r) * initPreviewOffsetY + r * finalPreviewOffsetY;
+                    item.setReorderPreviewOffset(x, y);
                 }
             });
             va.addListener(new AnimatorListenerAdapter() {
@@ -912,6 +926,7 @@
                     // place just yet.
                     if (!cancelled) {
                         lp.isLockedToGrid = true;
+                        item.setReorderPreviewOffset(0, 0);
                         child.requestLayout();
                     }
                     if (mReorderAnimators.containsKey(lp)) {
@@ -1930,10 +1945,10 @@
             finalDeltaX = 0;
             finalDeltaY = 0;
 
-            child.getReorderOffset(mTmpPointF);
+            child.getReorderBounceOffset(mTmpPointF);
             initDeltaX = mTmpPointF.x;
             initDeltaY = mTmpPointF.y;
-            initScale = child.getReorderScale();
+            initScale = child.getReorderBounceScale();
             finalScale = mChildScale - (CHILD_DIVIDEND / child.getView().getWidth()) * initScale;
 
             int dir = mode == MODE_HINT ? -1 : 1;
@@ -2010,9 +2025,9 @@
             float r1 = (mode == MODE_HINT && repeating) ? 1.0f : animationProgress;
             float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
             float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
-            child.setReorderOffset(x, y);
+            child.setReorderBounceOffset(x, y);
             float s = animationProgress * finalScale + (1 - animationProgress) * initScale;
-            child.setReorderScale(s);
+            child.setReorderBounceScale(s);
         }
 
         private void cancel() {
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 0a1fad1..63b90ae 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -17,6 +17,7 @@
 package com.android.launcher3;
 
 import static com.android.launcher3.Utilities.getDevicePrefs;
+import static com.android.launcher3.Utilities.getPointString;
 import static com.android.launcher3.config.FeatureFlags.APPLY_CONFIG_AT_RUNTIME;
 import static com.android.launcher3.settings.SettingsActivity.GRID_OPTIONS_PREFERENCE_KEY;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
@@ -69,6 +70,9 @@
     public static final MainThreadInitializedObject<InvariantDeviceProfile> INSTANCE =
             new MainThreadInitializedObject<>(InvariantDeviceProfile::new);
 
+    public static final String KEY_MIGRATION_SRC_WORKSPACE_SIZE = "migration_src_workspace_size";
+    public static final String KEY_MIGRATION_SRC_HOTSEAT_COUNT = "migration_src_hotseat_count";
+
     private static final String KEY_IDP_GRID_NAME = "idp_grid_name";
 
     private static final float ICON_SIZE_DEFINED_IN_APP_DP = 48;
@@ -165,6 +169,10 @@
         if (!newGridName.equals(gridName)) {
             Utilities.getPrefs(context).edit().putString(KEY_IDP_GRID_NAME, newGridName).apply();
         }
+        Utilities.getPrefs(context).edit()
+                .putInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, numHotseatIcons)
+                .putString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, getPointString(numColumns, numRows))
+                .apply();
 
         mConfigMonitor = new ConfigMonitor(context,
                 APPLY_CONFIG_AT_RUNTIME.get() ? this::onConfigChanged : this::killProcess);
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 8d20bd6..308d84f 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -146,7 +146,8 @@
      */
     protected synchronized void createDbIfNotExists() {
         if (mOpenHelper == null) {
-            mOpenHelper = DatabaseHelper.createDatabaseHelper(getContext());
+            mOpenHelper = DatabaseHelper.createDatabaseHelper(
+                    getContext(), false /* forMigration */);
 
             if (RestoreDbTask.isPending(getContext())) {
                 if (!RestoreDbTask.performRestore(getContext(), mOpenHelper,
@@ -430,7 +431,8 @@
                                     InvariantDeviceProfile.INSTANCE.get(getContext()).dbFile,
                                     Favorites.TMP_TABLE,
                                     () -> mOpenHelper,
-                                    () -> DatabaseHelper.createDatabaseHelper(getContext())));
+                                    () -> DatabaseHelper.createDatabaseHelper(
+                                            getContext(), true /* forMigration */)));
                     return result;
                 }
             }
@@ -441,7 +443,8 @@
                             prepForMigration(
                                     arg /* dbFile */,
                                     Favorites.PREVIEW_TABLE_NAME,
-                                    () -> DatabaseHelper.createDatabaseHelper(getContext(), arg),
+                                    () -> DatabaseHelper.createDatabaseHelper(
+                                            getContext(), arg, true /* forMigration */),
                                     () -> mOpenHelper));
                     return result;
                 }
@@ -609,20 +612,22 @@
     public static class DatabaseHelper extends NoLocaleSQLiteHelper implements
             LayoutParserCallback {
         private final Context mContext;
+        private final boolean mForMigration;
         private int mMaxItemId = -1;
         private int mMaxScreenId = -1;
         private boolean mBackupTableExists;
 
-        static DatabaseHelper createDatabaseHelper(Context context) {
-            return createDatabaseHelper(context, null);
+        static DatabaseHelper createDatabaseHelper(Context context, boolean forMigration) {
+            return createDatabaseHelper(context, null, forMigration);
         }
 
-        static DatabaseHelper createDatabaseHelper(Context context, String dbName) {
+        static DatabaseHelper createDatabaseHelper(Context context, String dbName,
+                boolean forMigration) {
             if (dbName == null) {
                 dbName = MULTI_DB_GRID_MIRATION_ALGO.get() ? InvariantDeviceProfile.INSTANCE.get(
                         context).dbFile : LauncherFiles.LAUNCHER_DB;
             }
-            DatabaseHelper databaseHelper = new DatabaseHelper(context, dbName);
+            DatabaseHelper databaseHelper = new DatabaseHelper(context, dbName, forMigration);
             // Table creation sometimes fails silently, which leads to a crash loop.
             // This way, we will try to create a table every time after crash, so the device
             // would eventually be able to recover.
@@ -643,9 +648,10 @@
         /**
          * Constructor used in tests and for restore.
          */
-        public DatabaseHelper(Context context, String dbName) {
+        public DatabaseHelper(Context context, String dbName, boolean forMigration) {
             super(context, dbName, SCHEMA_VERSION);
             mContext = context;
+            mForMigration = forMigration;
         }
 
         protected void initIds() {
@@ -670,7 +676,9 @@
 
             // Fresh and clean launcher DB.
             mMaxItemId = initializeMaxItemId(db);
-            onEmptyDbCreated();
+            if (!mForMigration) {
+                onEmptyDbCreated();
+            }
         }
 
         protected void onAddOrDeleteOp(SQLiteDatabase db) {
diff --git a/src/com/android/launcher3/Reorderable.java b/src/com/android/launcher3/Reorderable.java
index 5112eaf..047fb01 100644
--- a/src/com/android/launcher3/Reorderable.java
+++ b/src/com/android/launcher3/Reorderable.java
@@ -22,17 +22,24 @@
 public interface Reorderable {
 
     /**
-     * Set the offset related to reorder hint and "bounce" animations
+     * Set the offset related to reorder hint and bounce animations
      */
-    void setReorderOffset(float x, float y);
+    void setReorderBounceOffset(float x, float y);
 
-    void getReorderOffset(PointF offset);
+    void getReorderBounceOffset(PointF offset);
+
+    /**
+     * Set the offset related to previewing the new reordered position
+     */
+    void setReorderPreviewOffset(float x, float y);
+
+    void getReorderPreviewOffset(PointF offset);
 
     /**
      * Set the scale related to reorder hint and "bounce" animations
      */
-    void setReorderScale(float scale);
-    float getReorderScale();
+    void setReorderBounceScale(float scale);
+    float getReorderBounceScale();
 
     /**
      * Get the com.android.view related to this object
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 12d88df..c287190 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -332,7 +332,7 @@
                         .map(info -> info.suggestedFolderNames)
                         .map(folderNames -> (FolderNameInfo[]) folderNames
                                 .getParcelableArrayExtra(FolderInfo.EXTRA_FOLDER_SUGGESTIONS))
-                        .ifPresent(nameInfos -> showLabelSuggestions(nameInfos));
+                        .ifPresent(this::showLabelSuggestions);
             }
             mFolderName.setHint("");
             mIsEditingName = true;
@@ -1450,7 +1450,7 @@
             if (hasFocus) {
                 startEditingFolderName();
             } else {
-                logEditFolderLabel();
+                logCurrentFolderLabelState();
                 mFolderName.dispatchBackKey();
             }
         }
@@ -1649,7 +1649,7 @@
         return mContent;
     }
 
-    private void logEditFolderLabel() {
+    protected void logCurrentFolderLabelState() {
         LauncherEvent launcherEvent = LauncherEvent.newBuilder()
                 .setAction(Action.newBuilder().setType(Action.Type.SOFT_KEYBOARD))
                 .addSrcTarget(newEditTextTargetBuilder()
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 96bdc2a..b875a0b 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -121,8 +121,9 @@
     private float mDotScale;
     private Animator mDotScaleAnim;
 
-    private final PointF mTranslationForReorder = new PointF(0, 0);
-    private float mScaleForReorder = 1f;
+    private final PointF mTranslationForReorderBounce = new PointF(0, 0);
+    private final PointF mTranslationForReorderPreview = new PointF(0, 0);
+    private float mScaleForReorderBounce = 1f;
 
     private static final Property<FolderIcon, Float> DOT_SCALE_PROPERTY
             = new Property<FolderIcon, Float>(Float.TYPE, "dotScale") {
@@ -416,6 +417,7 @@
             mPreviewItemManager.hidePreviewItem(finalIndex, false);
             mFolder.showItem(item);
             setLabelSuggestion(nameInfos);
+            mFolder.logCurrentFolderLabelState();
             invalidate();
         }, DROP_IN_ANIMATION_DURATION);
     }
@@ -438,7 +440,6 @@
         onTitleChanged(mInfo.title);
         mFolder.mFolderName.setText(mInfo.title);
         mFolder.mLauncher.getModelWriter().updateItemInDatabase(mInfo);
-        // TODO: Add logging while folder creation.
     }
 
 
@@ -712,25 +713,39 @@
         mPreviewItemManager.onFolderClose(currentPage);
     }
 
-
-    public void setReorderOffset(float x, float y) {
-        mTranslationForReorder.set(x, y);
-        super.setTranslationX(x);
-        super.setTranslationY(y);
+    private void updateTranslation() {
+        super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x);
+        super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y);
     }
 
-    public void getReorderOffset(PointF offset) {
-        offset.set(mTranslationForReorder);
+    public void setReorderBounceOffset(float x, float y) {
+        mTranslationForReorderBounce.set(x, y);
+        updateTranslation();
     }
 
-    public void setReorderScale(float scale) {
-        mScaleForReorder = scale;
+    public void getReorderBounceOffset(PointF offset) {
+        offset.set(mTranslationForReorderBounce);
+    }
+
+    @Override
+    public void setReorderPreviewOffset(float x, float y) {
+        mTranslationForReorderPreview.set(x, y);
+        updateTranslation();
+    }
+
+    @Override
+    public void getReorderPreviewOffset(PointF offset) {
+        offset.set(mTranslationForReorderPreview);
+    }
+
+    public void setReorderBounceScale(float scale) {
+        mScaleForReorderBounce = scale;
         super.setScaleX(scale);
         super.setScaleY(scale);
     }
 
-    public float getReorderScale() {
-        return mScaleForReorder;
+    public float getReorderBounceScale() {
+        return mScaleForReorderBounce;
     }
 
     public View getView() {
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTask.java b/src/com/android/launcher3/model/GridSizeMigrationTask.java
index b27e4ea..e8a52bd 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTask.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTask.java
@@ -1,5 +1,7 @@
 package com.android.launcher3.model;
 
+import static com.android.launcher3.InvariantDeviceProfile.KEY_MIGRATION_SRC_HOTSEAT_COUNT;
+import static com.android.launcher3.InvariantDeviceProfile.KEY_MIGRATION_SRC_WORKSPACE_SIZE;
 import static com.android.launcher3.LauncherSettings.Settings.EXTRA_VALUE;
 import static com.android.launcher3.Utilities.getPointString;
 import static com.android.launcher3.Utilities.parsePoint;
@@ -53,9 +55,6 @@
     private static final String TAG = "GridSizeMigrationTask";
     private static final boolean DEBUG = true;
 
-    private static final String KEY_MIGRATION_SRC_WORKSPACE_SIZE = "migration_src_workspace_size";
-    private static final String KEY_MIGRATION_SRC_HOTSEAT_COUNT = "migration_src_hotseat_count";
-
     // These are carefully selected weights for various item types (Math.random?), to allow for
     // the least absurd migration experience.
     private static final float WT_SHORTCUT = 1;
@@ -894,8 +893,7 @@
         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);
+                || idp.numHotseatIcons != prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, -1);
     }
 
     /** See {@link #migrateGridIfNeeded(Context, InvariantDeviceProfile)} */
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
index 1c44fc3..4a28218 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.model;
 
+import static com.android.launcher3.InvariantDeviceProfile.KEY_MIGRATION_SRC_HOTSEAT_COUNT;
+import static com.android.launcher3.InvariantDeviceProfile.KEY_MIGRATION_SRC_WORKSPACE_SIZE;
 import static com.android.launcher3.Utilities.getPointString;
 import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
 
@@ -63,9 +65,6 @@
  */
 public class GridSizeMigrationTaskV2 {
 
-    public static final String KEY_MIGRATION_SRC_WORKSPACE_SIZE = "migration_src_workspace_size";
-    public static final String KEY_MIGRATION_SRC_HOTSEAT_COUNT = "migration_src_hotseat_count";
-
     private static final String TAG = "GridSizeMigrationTaskV2";
     private static final boolean DEBUG = true;
 
@@ -110,8 +109,7 @@
         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);
+                || idp.numHotseatIcons != prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, -1);
     }
 
     /** See {@link #migrateGridIfNeeded(Context, InvariantDeviceProfile)} */
@@ -148,14 +146,6 @@
 
         SharedPreferences prefs = Utilities.getPrefs(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.
-            return true;
-        }
-
         HashSet<String> validPackages = getValidPackages(context);
         int srcHotseatCount = prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, idp.numHotseatIcons);
 
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 0d97596..2fa6051 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -42,7 +42,6 @@
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.shortcuts.ShortcutRequest;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.FlagOp;
 import com.android.launcher3.util.IntSparseArrayMap;
 import com.android.launcher3.util.ItemInfoMatcher;
@@ -86,10 +85,6 @@
 
     @Override
     public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList appsList) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.APP_NOT_DISABLED, "PackageUpdatedTask: " + mOp + ", " +
-                    Arrays.toString(mPackages));
-        }
         final Context context = app.getContext();
         final IconCache iconCache = app.getIconCache();
 
diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java
index bd843e7..14f9a3e 100644
--- a/src/com/android/launcher3/model/data/ItemInfo.java
+++ b/src/com/android/launcher3/model/data/ItemInfo.java
@@ -37,6 +37,8 @@
 import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.util.ContentWriter;
 
+import java.util.Optional;
+
 /**
  * Represents an item in the launcher.
  */
@@ -248,24 +250,29 @@
 
         LauncherAtom.ItemInfo.Builder itemBuilder = LauncherAtom.ItemInfo.newBuilder();
         itemBuilder.setIsWork(user != Process.myUserHandle());
-        ComponentName cn = getTargetComponent();
+        Optional<ComponentName> nullableComponent = Optional.ofNullable(getTargetComponent());
         switch (itemType) {
             case ITEM_TYPE_APPLICATION:
-                itemBuilder.setApplication(LauncherAtom.Application.newBuilder()
-                        .setComponentName(cn.flattenToShortString())
-                        .setPackageName(cn.getPackageName()));
+                itemBuilder
+                        .setApplication(nullableComponent
+                                .map(component -> LauncherAtom.Application.newBuilder()
+                                        .setComponentName(component.flattenToShortString())
+                                        .setPackageName(component.getPackageName()))
+                                .orElse(LauncherAtom.Application.newBuilder()));
                 break;
             case ITEM_TYPE_DEEP_SHORTCUT:
             case ITEM_TYPE_SHORTCUT:
-                itemBuilder.setShortcut(LauncherAtom.Shortcut.newBuilder()
-                        .setShortcutName(cn.flattenToShortString()));
+                itemBuilder
+                        .setShortcut(nullableComponent
+                                .map(component -> LauncherAtom.Shortcut.newBuilder()
+                                        .setShortcutName(component.flattenToShortString()))
+                                .orElse(LauncherAtom.Shortcut.newBuilder()));
                 break;
             case ITEM_TYPE_APPWIDGET:
                 setItemBuilder(itemBuilder);
                 break;
             default:
                 break;
-
         }
         if (fInfo != null) {
             LauncherAtom.FolderContainer.Builder folderBuilder =
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 82f2eb4..a5a06b4 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -96,6 +96,4 @@
     public static final String REQUEST_MOCK_SENSOR_ROTATION = "mock-sensor-rotation";
 
     public static final String PERMANENT_DIAG_TAG = "TaplTarget";
-
-    public static final String APP_NOT_DISABLED = "b/139891609";
 }
diff --git a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
index 5ea01d9..a4e7daa 100644
--- a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
@@ -46,8 +46,9 @@
      */
     private final PointF mTranslationForCentering = new PointF(0, 0);
 
-    private final PointF mTranslationForReorder = new PointF(0, 0);
-    private float mScaleForReorder = 1f;
+    private final PointF mTranslationForReorderBounce = new PointF(0, 0);
+    private final PointF mTranslationForReorderPreview = new PointF(0, 0);
+    private float mScaleForReorderBounce = 1f;
 
     @ViewDebug.ExportedProperty(category = "launcher")
     private boolean mChildrenFocused;
@@ -152,45 +153,63 @@
         setSelected(childIsFocused);
     }
 
-    public void setScaleToFit(float scale) {
-        mScaleToFit = scale;
-        super.setScaleX(scale * mScaleForReorder);
-        super.setScaleY(scale * mScaleForReorder);
-    }
-
-    public float getScaleToFit() {
-        return mScaleToFit;
-    }
-
     public View getView() {
         return this;
     }
 
+    private void updateTranslation() {
+        super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x
+                + mTranslationForCentering.x);
+        super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y
+                + mTranslationForCentering.y);
+    }
 
     public void setTranslationForCentering(float x, float y) {
         mTranslationForCentering.set(x, y);
-        super.setTranslationX(x + mTranslationForReorder.x);
-        super.setTranslationY(y + mTranslationForReorder.y);
+        updateTranslation();
     }
 
-    public void setReorderOffset(float x, float y) {
-        mTranslationForReorder.set(x, y);
-        super.setTranslationX(mTranslationForCentering.x + x);
-        super.setTranslationY(mTranslationForCentering.y + y);
+    public void setReorderBounceOffset(float x, float y) {
+        mTranslationForReorderBounce.set(x, y);
+        updateTranslation();
     }
 
-    public void getReorderOffset(PointF offset) {
-        offset.set(mTranslationForReorder);
+    public void getReorderBounceOffset(PointF offset) {
+        offset.set(mTranslationForReorderBounce);
     }
 
-    public void setReorderScale(float scale) {
-        mScaleForReorder = scale;
-        super.setScaleX(mScaleToFit * scale);
-        super.setScaleY(mScaleToFit * scale);
+    @Override
+    public void setReorderPreviewOffset(float x, float y) {
+        mTranslationForReorderPreview.set(x, y);
+        updateTranslation();
     }
 
-    public float getReorderScale() {
-        return mScaleForReorder;
+    @Override
+    public void getReorderPreviewOffset(PointF offset) {
+        offset.set(mTranslationForReorderPreview);
+    }
+
+    private void updateScale() {
+        super.setScaleX(mScaleToFit * mScaleForReorderBounce);
+        super.setScaleY(mScaleToFit * mScaleForReorderBounce);
+    }
+
+    public void setReorderBounceScale(float scale) {
+        mScaleForReorderBounce = scale;
+        updateScale();
+    }
+
+    public float getReorderBounceScale() {
+        return mScaleForReorderBounce;
+    }
+
+    public void setScaleToFit(float scale) {
+        mScaleToFit = scale;
+        updateScale();
+    }
+
+    public float getScaleToFit() {
+        return mScaleToFit;
     }
 
     @Override
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 0ba5a36..9c8e278 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -98,9 +98,10 @@
     public static final long DEFAULT_UI_TIMEOUT = 10000;
     private static final String TAG = "AbstractLauncherUiTest";
 
-    private static String sDetectedActivityLeak;
+    private static String sStrictmodeDetectedActivityLeak;
     private static boolean sActivityLeakReported;
     private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
+    private static final ActivityLeakTracker ACTIVITY_LEAK_TRACKER = new ActivityLeakTracker();
 
     protected LooperExecutor mMainThreadExecutor = MAIN_EXECUTOR;
     protected final UiDevice mDevice = UiDevice.getInstance(getInstrumentation());
@@ -113,30 +114,51 @@
         if (TestHelpers.isInLauncherProcess()) {
             StrictMode.VmPolicy.Builder builder =
                     new StrictMode.VmPolicy.Builder()
-                            .detectActivityLeaks()
+// b/154772063
+//                            .detectActivityLeaks()
                             .penaltyLog()
                             .penaltyListener(Runnable::run, violation -> {
-                                // Runs in the main thread. We can't dumpheap in the main thread,
-                                // so let's just mark the fact that the leak has happened.
-                                if (sDetectedActivityLeak == null) {
-                                    sDetectedActivityLeak = violation.toString();
-                                    try {
-                                        Debug.dumpHprofData(
-                                                getInstrumentation().getTargetContext()
-                                                        .getFilesDir().getPath()
-                                                        + "/ActivityLeakHeapDump.hprof");
-                                    } catch (Throwable e) {
-                                        Log.e(TAG, "dumpHprofData failed", e);
-                                    }
+                                if (sStrictmodeDetectedActivityLeak == null) {
+                                    sStrictmodeDetectedActivityLeak = violation.toString() + ", "
+                                            + dumpHprofData() + ".";
                                 }
                             });
             StrictMode.setVmPolicy(builder.build());
         }
     }
 
-    public static void checkDetectedLeaks() {
-        if (sDetectedActivityLeak != null && !sActivityLeakReported) {
+    public static void checkDetectedLeaks(LauncherInstrumentation launcher) {
+        if (sActivityLeakReported) return;
+
+        if (sStrictmodeDetectedActivityLeak != null) {
+            // Report from the test thread strictmode violations detected in the main thread.
             sActivityLeakReported = true;
+            Assert.fail(sStrictmodeDetectedActivityLeak);
+        }
+
+        // Check whether activity leak detector has found leaked activities.
+        Wait.atMost(AbstractLauncherUiTest::getActivityLeakErrorMessage,
+                () -> {
+                    launcher.getTotalPssKb();  // Triggers GC
+                    return MAIN_EXECUTOR.submit(
+                            () -> ACTIVITY_LEAK_TRACKER.noLeakedActivities()).get();
+                }, DEFAULT_UI_TIMEOUT, launcher);
+    }
+
+    private static String getActivityLeakErrorMessage() {
+        sActivityLeakReported = true;
+        return "Activity leak detector has found leaked activities, " + dumpHprofData() + ".";
+    }
+
+    private static String dumpHprofData() {
+        try {
+            final String fileName = getInstrumentation().getTargetContext().getFilesDir().getPath()
+                    + "/ActivityLeakHeapDump.hprof";
+            Debug.dumpHprofData(fileName);
+            return "memory dump filename: " + fileName;
+        } catch (Throwable e) {
+            Log.e(TAG, "dumpHprofData failed", e);
+            return "failed to save memory dump";
         }
     }
 
@@ -263,7 +285,7 @@
         if (mLauncherPid != 0) {
             assertEquals("Launcher crashed, pid mismatch:", mLauncherPid, mLauncher.getPid());
         }
-        checkDetectedLeaks();
+        checkDetectedLeaks(mLauncher);
     }
 
     protected void clearLauncherData() throws IOException, InterruptedException {
diff --git a/tests/src/com/android/launcher3/ui/ActivityLeakTracker.java b/tests/src/com/android/launcher3/ui/ActivityLeakTracker.java
new file mode 100644
index 0000000..e9258e9
--- /dev/null
+++ b/tests/src/com/android/launcher3/ui/ActivityLeakTracker.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.ui;
+
+import android.app.Activity;
+import android.app.Application;
+import android.os.Bundle;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.launcher3.tapl.TestHelpers;
+
+import java.util.WeakHashMap;
+
+class ActivityLeakTracker implements Application.ActivityLifecycleCallbacks {
+    private final WeakHashMap<Activity, Boolean> mActivities = new WeakHashMap<>();
+
+    ActivityLeakTracker() {
+        if (!TestHelpers.isInLauncherProcess()) return;
+        final Application app =
+                (Application) InstrumentationRegistry.getTargetContext().getApplicationContext();
+        app.registerActivityLifecycleCallbacks(this);
+    }
+
+    @Override
+    public void onActivityCreated(Activity activity, Bundle bundle) {
+        mActivities.put(activity, true);
+    }
+
+    @Override
+    public void onActivityStarted(Activity activity) {
+    }
+
+    @Override
+    public void onActivityResumed(Activity activity) {
+    }
+
+    @Override
+    public void onActivityPaused(Activity activity) {
+    }
+
+    @Override
+    public void onActivityStopped(Activity activity) {
+    }
+
+    @Override
+    public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
+    }
+
+    @Override
+    public void onActivityDestroyed(Activity activity) {
+    }
+
+    public boolean noLeakedActivities() {
+        int liveActivities = 0;
+        int destroyedActivities = 0;
+
+        for (Activity activity : mActivities.keySet()) {
+            if (activity.isDestroyed()) {
+                ++destroyedActivities;
+            } else {
+                ++liveActivities;
+            }
+        }
+
+        if (liveActivities > 2)  return false;
+
+        // It's OK to have 1 leaked activity if no active activities exist.
+        return liveActivities == 0 ? destroyedActivities <= 1 : destroyedActivities == 0;
+    }
+}
diff --git a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
index 38f50c1..266f0ae 100644
--- a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
+++ b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
@@ -56,7 +56,7 @@
             private void evaluateInPortrait() throws Throwable {
                 mTest.mDevice.setOrientationNatural();
                 mTest.mLauncher.setExpectedRotation(Surface.ROTATION_0);
-                AbstractLauncherUiTest.checkDetectedLeaks();
+                AbstractLauncherUiTest.checkDetectedLeaks(mTest.mLauncher);
                 base.evaluate();
                 mTest.getDevice().pressHome();
             }
@@ -64,7 +64,7 @@
             private void evaluateInLandscape() throws Throwable {
                 mTest.mDevice.setOrientationLeft();
                 mTest.mLauncher.setExpectedRotation(Surface.ROTATION_90);
-                AbstractLauncherUiTest.checkDetectedLeaks();
+                AbstractLauncherUiTest.checkDetectedLeaks(mTest.mLauncher);
                 base.evaluate();
                 mTest.getDevice().pressHome();
             }
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 57000a0..34e425d 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -64,7 +64,7 @@
         test.waitForResumed("Launcher internal state is still Background");
         // Check that we switched to home.
         test.mLauncher.getWorkspace();
-        AbstractLauncherUiTest.checkDetectedLeaks();
+        AbstractLauncherUiTest.checkDetectedLeaks(test.mLauncher);
     }
 
     // Please don't add negative test cases for methods that fail only after a long wait.