Merge "Add NPE check for prediction controller" into ub-launcher3-rvc-dev
diff --git a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
index 0b7d4bf..e649ce1 100644
--- a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
+++ b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
@@ -23,6 +23,7 @@
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Color;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.Debug;
 import android.system.Os;
@@ -171,11 +172,16 @@
             }
 
             case TestProtocol.REQUEST_CLEAR_DATA: {
-                LauncherSettings.Settings.call(mContext.getContentResolver(),
-                        LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
-                MAIN_EXECUTOR.submit(() ->
-                        LauncherAppState.getInstance(mContext).getModel().forceReload());
-                return response;
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    LauncherSettings.Settings.call(mContext.getContentResolver(),
+                            LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
+                    MAIN_EXECUTOR.submit(() ->
+                            LauncherAppState.getInstance(mContext).getModel().forceReload());
+                    return response;
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
             }
 
             default:
diff --git a/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml b/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml
index 36c9b00..1dab482 100644
--- a/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml
+++ b/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml
@@ -90,6 +90,7 @@
                         android:layout_height="wrap_content"
                         android:background="?android:attr/selectableItemBackground"
                         android:text="@string/hotseat_edu_dismiss"
+                        android:layout_gravity="start|center_vertical"
                         android:textColor="@android:color/white"/>
                 </FrameLayout>
                 <FrameLayout
@@ -102,7 +103,7 @@
                         android:layout_width="wrap_content"
                         android:layout_height="wrap_content"
                         android:background="?android:attr/selectableItemBackground"
-                        android:gravity="end"
+                        android:layout_gravity="end|center_vertical"
                         android:text="@string/hotseat_edu_accept"
                         android:textColor="@android:color/white"/>
                 </FrameLayout>
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
index a1218ae..4f95254 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
@@ -47,7 +47,6 @@
  */
 public class HotseatEduController {
 
-    public static final String KEY_HOTSEAT_EDU_SEEN = "hotseat_edu_seen";
     public static final String HOTSEAT_EDU_ACTION =
             "com.android.launcher3.action.SHOW_HYBRID_HOTSEAT_EDU";
     public static final String SETTINGS_ACTION =
@@ -229,7 +228,6 @@
 
     void finishOnboarding() {
         mOnOnboardingComplete.run();
-        mLauncher.getSharedPrefs().edit().putBoolean(KEY_HOTSEAT_EDU_SEEN, true).apply();
     }
 
     void showDimissTip() {
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index 7334d80..1dbb9e2 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -155,14 +155,6 @@
     }
 
     /**
-     * Returns whether or not user has seen hybrid hotseat education
-     */
-    public boolean isEduSeen() {
-        return mLauncher.getSharedPrefs().getBoolean(HotseatEduController.KEY_HOTSEAT_EDU_SEEN,
-                false);
-    }
-
-    /**
      * Shows appropriate hotseat education based on prediction enabled and migration states.
      */
     public void showEdu() {
@@ -172,7 +164,7 @@
                 Snackbar.show(mLauncher, R.string.hotsaet_tip_prediction_disabled,
                         R.string.hotseat_prediction_settings, null,
                         () -> mLauncher.startActivity(getSettingsIntent()));
-            } else if (isEduSeen() || getPredictedIcons().size() >= (mHotSeatItemsCount + 1) / 2) {
+            } else if (getPredictedIcons().size() >= (mHotSeatItemsCount + 1) / 2) {
                 showDiscoveryTip();
             } else {
                 HotseatEduController eduController = new HotseatEduController(mLauncher,
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
index 02bae64..a63f3a8 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
@@ -327,7 +327,7 @@
     public abstract void onMotionPauseChanged(boolean isPaused);
 
     @UiThread
-    public void onGestureStarted() { }
+    public void onGestureStarted(boolean isLikelyToStartNewTask) { }
 
     @UiThread
     public abstract void onGestureCancelled();
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java
index 413a813..37aa0da 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java
@@ -492,9 +492,13 @@
 
     @Override
     public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) {
+        setIsLikelyToStartNewTask(isLikelyToStartNewTask, true /* animate */);
+    }
+
+    private void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask, boolean animate) {
         if (mIsLikelyToStartNewTask != isLikelyToStartNewTask) {
             mIsLikelyToStartNewTask = isLikelyToStartNewTask;
-            maybeUpdateRecentsAttachedState();
+            maybeUpdateRecentsAttachedState(animate);
         }
     }
 
@@ -628,8 +632,9 @@
     }
 
     @Override
-    public void onGestureStarted() {
+    public void onGestureStarted(boolean isLikelyToStartNewTask) {
         notifyGestureStartedAsync();
+        setIsLikelyToStartNewTask(isLikelyToStartNewTask, false /* animate */);
         mStateCallback.setStateOnUiThread(STATE_GESTURE_STARTED);
         mGestureStarted = true;
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
index 01936e4..e9614d1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
@@ -148,7 +148,7 @@
                 boolean rotated) {
             final boolean isAllowedByPolicy = thumbnail.isRealSnapshot;
 
-            mActionsView.updateDisabledFlags(DISABLED_ROTATED, rotated);
+            getActionsView().updateDisabledFlags(DISABLED_ROTATED, rotated);
 
             getActionsView().setCallbacks(new OverlayUICallbacks() {
                 @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index 4e967cf..26df9c7 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -277,6 +277,13 @@
                 if (!mPassedSlopOnThisGesture && passedSlop) {
                     mPassedSlopOnThisGesture = true;
                 }
+                // Until passing slop, we don't know what direction we're going, so assume
+                // we're quick switching to avoid translating recents away when continuing
+                // the gesture (in which case mPassedPilferInputSlop starts as true).
+                boolean haveNotPassedSlopOnContinuedGesture =
+                        !mPassedSlopOnThisGesture && mPassedPilferInputSlop;
+                boolean isLikelyToStartNewTask = haveNotPassedSlopOnContinuedGesture
+                        || horizontalDist > upDist;
 
                 if (!mPassedPilferInputSlop) {
                     if (passedSlop) {
@@ -299,7 +306,7 @@
                             mStartDisplacement = Math.min(displacement, -mTouchSlop);
 
                         }
-                        notifyGestureStarted();
+                        notifyGestureStarted(isLikelyToStartNewTask);
                     }
                 }
 
@@ -310,13 +317,6 @@
                     }
 
                     if (mDeviceState.isFullyGesturalNavMode()) {
-                        // Until passing slop, we don't know what direction we're going, so assume
-                        // we're quick switching to avoid translating recents away when continuing
-                        // the gesture.
-                        boolean haveNotPassedSlopOnContinuedGesture =
-                                !mPassedSlopOnThisGesture && mPassedPilferInputSlop;
-                        boolean isLikelyToStartNewTask = haveNotPassedSlopOnContinuedGesture
-                                || horizontalDist > upDist;
                         mMotionPauseDetector.setDisallowPause(upDist < mMotionPauseMinDisplacement
                                 || isLikelyToStartNewTask);
                         mMotionPauseDetector.addPosition(ev);
@@ -340,7 +340,7 @@
         }
     }
 
-    private void notifyGestureStarted() {
+    private void notifyGestureStarted(boolean isLikelyToStartNewTask) {
         ActiveGestureLog.INSTANCE.addLog("startQuickstep");
         if (mInteractionHandler == null) {
             return;
@@ -353,7 +353,7 @@
                 CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
 
         // Notify the handler that the gesture has actually started
-        mInteractionHandler.onGestureStarted();
+        mInteractionHandler.onGestureStarted(isLikelyToStartNewTask);
     }
 
     private void startTouchTrackingForWindowAnimation(long touchTimeMs) {
@@ -370,8 +370,7 @@
             mActiveCallbacks = mTaskAnimationManager.continueRecentsAnimation(mGestureState);
             mActiveCallbacks.addListener(mInteractionHandler);
             mTaskAnimationManager.notifyRecentsAnimationState(mInteractionHandler);
-            mInteractionHandler.setIsLikelyToStartNewTask(true);
-            notifyGestureStarted();
+            notifyGestureStarted(true /*isLikelyToStartNewTask*/);
         } else {
             intent.putExtra(INTENT_EXTRA_LOG_TRACE_ID, mGestureState.getGestureId());
             mActiveCallbacks = mTaskAnimationManager.startRecentsAnimation(mGestureState, intent,
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java
index 512bbac..ef66b7a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java
@@ -235,7 +235,8 @@
         Rect insets = mActivity.getDragLayer().getInsets();
         BaseDragLayer.LayoutParams params = (BaseDragLayer.LayoutParams) getLayoutParams();
         params.width = orientationHandler.getTaskMenuWidth(taskView.getThumbnail());
-        params.gravity = Gravity.START;
+        // Gravity set to Left instead of Start as sTempRect.left measures Left distance not Start
+        params.gravity = Gravity.LEFT;
         setLayoutParams(params);
         setScaleX(taskView.getScaleX());
         setScaleY(taskView.getScaleY());
diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
index 53f37c1..22d205a 100644
--- a/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
+++ b/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
@@ -59,6 +59,7 @@
         mResources = mock(Resources.class);
         when(mResources.getBoolean(anyInt())).thenReturn(true);
         when(mResources.getDimension(anyInt())).thenReturn(10.0f);
+        when(mResources.getDimensionPixelSize(anyInt())).thenReturn(10);
         DisplayMetrics mockDisplayMetrics = new DisplayMetrics();
         mockDisplayMetrics.density = DENSITY_DISPLAY_METRICS;
         when(mResources.getDisplayMetrics()).thenReturn(mockDisplayMetrics);
@@ -67,53 +68,114 @@
     }
 
     @Test
-    public void disabledMultipeRegions_shouldOverrideFirstRegion() {
-        mTouchTransformer.createOrAddTouchRegion(mInfo);
-        DefaultDisplay.Info info2 = createDisplayInfo(Surface.ROTATION_90);
-        mTouchTransformer.createOrAddTouchRegion(info2);
+    public void disabledMultipleRegions_shouldOverrideFirstRegion() {
+        float portraitRegionY = generateTouchRegionHeight(Surface.ROTATION_0) + 1;
+        float landscapeRegionY = generateTouchRegionHeight(Surface.ROTATION_90) + 1;
 
-        float y = generateTouchRegionHeight(Surface.ROTATION_0) + 1;
-        MotionEvent inOldRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, y);
-        mTouchTransformer.transform(inOldRegion);
-        assertFalse(mTouchTransformer.touchInValidSwipeRegions(inOldRegion.getX(), inOldRegion.getY()));
+        mTouchTransformer.createOrAddTouchRegion(mInfo);
+        tapAndAssertTrue(100, portraitRegionY,
+                event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
+        tapAndAssertFalse(100, landscapeRegionY,
+                event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
+        tapAndAssertTrue(0, portraitRegionY,
+                event -> mTouchTransformer.touchInAssistantRegion(event));
+        tapAndAssertFalse(0, landscapeRegionY,
+                event -> mTouchTransformer.touchInAssistantRegion(event));
 
         // Override region
+        mTouchTransformer.createOrAddTouchRegion(createDisplayInfo(Surface.ROTATION_90));
+        tapAndAssertFalse(100, portraitRegionY,
+                event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
+        tapAndAssertTrue(100, landscapeRegionY,
+                event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
+        tapAndAssertFalse(0, portraitRegionY,
+                event -> mTouchTransformer.touchInAssistantRegion(event));
+        tapAndAssertTrue(0, landscapeRegionY,
+                event -> mTouchTransformer.touchInAssistantRegion(event));
+
+        // Override region again
         mTouchTransformer.createOrAddTouchRegion(mInfo);
-        inOldRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, y);
-        mTouchTransformer.transform(inOldRegion);
-        assertTrue(mTouchTransformer.touchInValidSwipeRegions(inOldRegion.getX(), inOldRegion.getY()));
+        tapAndAssertTrue(100, portraitRegionY,
+                event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
+        tapAndAssertFalse(100, landscapeRegionY,
+                event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
+        tapAndAssertTrue(0, portraitRegionY,
+                event -> mTouchTransformer.touchInAssistantRegion(event));
+        tapAndAssertFalse(0, landscapeRegionY,
+                event -> mTouchTransformer.touchInAssistantRegion(event));
     }
 
     @Test
-    public void allowMultipeRegions_shouldOverrideFirstRegion() {
-        DefaultDisplay.Info info2 = createDisplayInfo(Surface.ROTATION_90);
-        mTouchTransformer.createOrAddTouchRegion(info2);
+    public void enableMultipleRegions_shouldOverrideFirstRegion() {
+        float portraitRegionY = generateTouchRegionHeight(Surface.ROTATION_0) + 1;
+        float landscapeRegionY = generateTouchRegionHeight(Surface.ROTATION_90) + 1;
+
+        mTouchTransformer.createOrAddTouchRegion(createDisplayInfo(Surface.ROTATION_90));
+        tapAndAssertFalse(100, portraitRegionY,
+                event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
+        tapAndAssertTrue(100, landscapeRegionY,
+                event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
+        tapAndAssertFalse(0, portraitRegionY,
+                event -> mTouchTransformer.touchInAssistantRegion(event));
+        tapAndAssertTrue(0, landscapeRegionY,
+                event -> mTouchTransformer.touchInAssistantRegion(event));
         // We have to add 0 rotation second so that gets set as the current rotation, otherwise
         // matrix transform will fail (tests only work in Portrait at the moment)
         mTouchTransformer.enableMultipleRegions(true, mInfo);
         mTouchTransformer.createOrAddTouchRegion(mInfo);
 
-        float y = generateTouchRegionHeight(Surface.ROTATION_0) + 1;
-        MotionEvent inNewRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, y);
-        mTouchTransformer.transform(inNewRegion);
-        assertTrue(mTouchTransformer.touchInValidSwipeRegions(inNewRegion.getX(), inNewRegion.getY()));
+        tapAndAssertTrue(100, portraitRegionY,
+                event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
+        tapAndAssertFalse(100, landscapeRegionY,
+                event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
+        tapAndAssertTrue(0, portraitRegionY,
+                event -> mTouchTransformer.touchInAssistantRegion(event));
+        tapAndAssertFalse(0, landscapeRegionY,
+                event -> mTouchTransformer.touchInAssistantRegion(event));
+    }
+
+    @Test
+    public void enableMultipleRegions_assistantTriggersInMostRecent() {
+        float portraitRegionY = generateTouchRegionHeight(Surface.ROTATION_0) + 1;
+        float landscapeRegionY = generateTouchRegionHeight(Surface.ROTATION_90) + 1;
+
+        mTouchTransformer.enableMultipleRegions(true, mInfo);
+        mTouchTransformer.createOrAddTouchRegion(createDisplayInfo(Surface.ROTATION_90));
+        mTouchTransformer.createOrAddTouchRegion(mInfo);
+        tapAndAssertTrue(0, portraitRegionY,
+                event -> mTouchTransformer.touchInAssistantRegion(event));
+        tapAndAssertFalse(0, landscapeRegionY,
+                event -> mTouchTransformer.touchInAssistantRegion(event));
+    }
+
+    @Test
+    public void enableMultipleRegions_assistantTriggersInCurrentOrientationAfterDisable() {
+        float portraitRegionY = generateTouchRegionHeight(Surface.ROTATION_0) + 1;
+        float landscapeRegionY = generateTouchRegionHeight(Surface.ROTATION_90) + 1;
+
+        mTouchTransformer.enableMultipleRegions(true, mInfo);
+        mTouchTransformer.createOrAddTouchRegion(mInfo);
+        mTouchTransformer.createOrAddTouchRegion(createDisplayInfo(Surface.ROTATION_90));
+        mTouchTransformer.enableMultipleRegions(false, mInfo);
+        tapAndAssertTrue(0, portraitRegionY,
+                event -> mTouchTransformer.touchInAssistantRegion(event));
+        tapAndAssertFalse(0, landscapeRegionY,
+                event -> mTouchTransformer.touchInAssistantRegion(event));
     }
 
     @Test
     public void applyTransform_taskNotFrozen_notInRegion() {
         mTouchTransformer.createOrAddTouchRegion(mInfo);
-        MotionEvent outOfRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, 100);
-        mTouchTransformer.transform(outOfRegion);
-        assertFalse(mTouchTransformer.touchInValidSwipeRegions(outOfRegion.getX(), outOfRegion.getY()));
+        tapAndAssertFalse(100, 100,
+                event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
     }
 
     @Test
     public void applyTransform_taskFrozen_noRotate_outOfRegion() {
         mTouchTransformer.createOrAddTouchRegion(mInfo);
         mTouchTransformer.enableMultipleRegions(true, mInfo);
-        MotionEvent outOfRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, 100);
-        mTouchTransformer.transform(outOfRegion);
-        assertFalse(mTouchTransformer.touchInValidSwipeRegions(outOfRegion.getX(), outOfRegion.getY()));
+        tapAndAssertFalse(100, 100,
+                event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
     }
 
     @Test
@@ -121,27 +183,24 @@
         mTouchTransformer.createOrAddTouchRegion(mInfo);
         mTouchTransformer.enableMultipleRegions(true, mInfo);
         float y = generateTouchRegionHeight(Surface.ROTATION_0) + 1;
-        MotionEvent inRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, y);
-        mTouchTransformer.transform(inRegion);
-        assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion.getX(), inRegion.getY()));
+        tapAndAssertTrue(100, y,
+                event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
     }
 
     @Test
     public void applyTransform_taskNotFrozen_noRotate_inDefaultRegion() {
         mTouchTransformer.createOrAddTouchRegion(mInfo);
         float y = generateTouchRegionHeight(Surface.ROTATION_0) + 1;
-        MotionEvent inRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, y);
-        mTouchTransformer.transform(inRegion);
-        assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion.getX(), inRegion.getY()));
+        tapAndAssertTrue(100, y,
+                event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
     }
 
     @Test
     public void applyTransform_taskNotFrozen_90Rotate_inRegion() {
         mTouchTransformer.createOrAddTouchRegion(createDisplayInfo(Surface.ROTATION_90));
         float y = generateTouchRegionHeight(Surface.ROTATION_90) + 1;
-        MotionEvent inRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, y);
-        mTouchTransformer.transform(inRegion);
-        assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion.getX(), inRegion.getY()));
+        tapAndAssertTrue(100, y,
+                event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
     }
 
     @Test
@@ -160,15 +219,15 @@
         MotionEvent inRegion2 = generateMotionEvent(MotionEvent.ACTION_DOWN, 10, 10);
         mTouchTransformer.transform(inRegion1_down);
         mTouchTransformer.transform(inRegion2);
-        assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion1_down.getX(), inRegion1_down.getY()));
+        assertTrue(mTouchTransformer.touchInValidSwipeRegions(
+                inRegion1_down.getX(), inRegion1_down.getY()));
         // We only process one gesture region until we see a MotionEvent.ACTION_UP
         assertFalse(mTouchTransformer.touchInValidSwipeRegions(inRegion2.getX(), inRegion2.getY()));
 
         mTouchTransformer.transform(inRegion1_up);
 
         // Set the new region with this MotionEvent.ACTION_DOWN
-        inRegion2 = generateMotionEvent(MotionEvent.ACTION_DOWN, 10, 370);
-        mTouchTransformer.transform(inRegion2);
+        inRegion2 = generateAndTransformMotionEvent(MotionEvent.ACTION_DOWN, 10, 370);
         assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion2.getX(), inRegion2.getY()));
     }
 
@@ -191,4 +250,26 @@
     private MotionEvent generateMotionEvent(int motionAction, float x, float y) {
         return MotionEvent.obtain(0, 0, motionAction, x, y, 0);
     }
+
+    private MotionEvent generateAndTransformMotionEvent(int motionAction, float x, float y) {
+        MotionEvent motionEvent = generateMotionEvent(motionAction, x, y);
+        mTouchTransformer.transform(motionEvent);
+        return motionEvent;
+    }
+
+    private void tapAndAssertTrue(float x, float y, MotionEventAssertion assertion) {
+        MotionEvent motionEvent = generateAndTransformMotionEvent(MotionEvent.ACTION_DOWN, x, y);
+        assertTrue(assertion.getCondition(motionEvent));
+        generateAndTransformMotionEvent(MotionEvent.ACTION_UP, x, y);
+    }
+
+    private void tapAndAssertFalse(float x, float y, MotionEventAssertion assertion) {
+        MotionEvent motionEvent = generateAndTransformMotionEvent(MotionEvent.ACTION_DOWN, x, y);
+        assertFalse(assertion.getCondition(motionEvent));
+        generateAndTransformMotionEvent(MotionEvent.ACTION_UP, x, y);
+    }
+
+    private interface MotionEventAssertion {
+        boolean getCondition(MotionEvent motionEvent);
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
index 3375c53..1081548 100644
--- a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
+++ b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
@@ -68,13 +68,13 @@
     private Resources mResources;
     private OrientationRectF mLastRectTouched;
     /**
-     * The rotation of the last touched nav bar. Derived from {@link #mLastRectTouched}, but has a
-     * longer lifetime than the rect. Note this is different than {@link #mQuickStepStartingRotation}
-     * as it always updates its value on every touch whereas mQuickstepStartingRotation only
-     * updates when device rotation matches touch rotation. Maybe this will be only one necessary
-     * after TODO(b/154580671) is in. TBD.
+     * The rotation of the last touched nav bar, whether that be through the last region the user
+     * touched down on or valid rotation user turned their device to.
+     * Note this is different than
+     * {@link #mQuickStepStartingRotation} as it always updates its value on every touch whereas
+     * mQuickstepStartingRotation only updates when device rotation matches touch rotation.
      */
-    private int mLastRectRotation;
+    private int mActiveTouchRotation;
     private SysUINavigationMode.Mode mMode;
     private QuickStepContractInfo mContractInfo;
 
@@ -159,13 +159,27 @@
         if (mEnableMultipleRegions) {
             mQuickStepStartingRotation = info.rotation;
         } else {
-            mLastRectRotation = 0;
+            mActiveTouchRotation = 0;
             mQuickStepStartingRotation = QUICKSTEP_ROTATION_UNINITIALIZED;
         }
         resetSwipeRegions(info);
     }
 
     /**
+     * Call when removing multiple regions to swipe from, but still in active quickswitch mode (task
+     * list is still frozen).
+     * Ex. This would be called when user has quickswitched to the same app rotation that
+     * they started quickswitching in, indicating that extra nav regions can be ignored. Calling
+     * this will update the value of {@link #mActiveTouchRotation}
+     *
+     * @param displayInfo The display whos rotation will be used as the current active rotation
+     */
+    void setSingleActiveRegion(DefaultDisplay.Info displayInfo) {
+        mActiveTouchRotation = displayInfo.rotation;
+        resetSwipeRegions(displayInfo);
+    }
+
+    /**
      * Only saves the swipe region represented by {@param region}, clears the
      * rest from {@link #mSwipeTouchRegions}
      * To be called whenever we want to stop tracking more than one swipe region.
@@ -178,15 +192,21 @@
 
         mCurrentDisplayRotation = region.rotation;
         OrientationRectF regionToKeep = mSwipeTouchRegions.get(mCurrentDisplayRotation);
+        if (regionToKeep == null) {
+            regionToKeep = createRegionForDisplay(region);
+        }
         mSwipeTouchRegions.clear();
-        mSwipeTouchRegions.put(mCurrentDisplayRotation,
-                regionToKeep != null ? regionToKeep : createRegionForDisplay(region));
+        mSwipeTouchRegions.put(mCurrentDisplayRotation, regionToKeep);
+        updateAssistantRegions(regionToKeep);
     }
 
     private void resetSwipeRegions() {
         OrientationRectF regionToKeep = mSwipeTouchRegions.get(mCurrentDisplayRotation);
         mSwipeTouchRegions.clear();
-        mSwipeTouchRegions.put(mCurrentDisplayRotation, regionToKeep);
+        if (regionToKeep != null) {
+            mSwipeTouchRegions.put(mCurrentDisplayRotation, regionToKeep);
+            updateAssistantRegions(regionToKeep);
+        }
     }
 
     private OrientationRectF createRegionForDisplay(DefaultDisplay.Info display) {
@@ -201,20 +221,7 @@
         if (mMode == SysUINavigationMode.Mode.NO_BUTTON) {
             int touchHeight = getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
             orientationRectF.top = orientationRectF.bottom - touchHeight;
-
-            final int assistantWidth = mResources
-                    .getDimensionPixelSize(R.dimen.gestures_assistant_width);
-            final float assistantHeight = Math.max(touchHeight,
-                    mContractInfo.getWindowCornerRadius());
-            mAssistantLeftRegion.bottom = mAssistantRightRegion.bottom = orientationRectF.bottom;
-            mAssistantLeftRegion.top = mAssistantRightRegion.top =
-                    orientationRectF.bottom - assistantHeight;
-
-            mAssistantLeftRegion.left = 0;
-            mAssistantLeftRegion.right = assistantWidth;
-
-            mAssistantRightRegion.right = orientationRectF.right;
-            mAssistantRightRegion.left = orientationRectF.right - assistantWidth;
+            updateAssistantRegions(orientationRectF);
         } else {
             mAssistantLeftRegion.setEmpty();
             mAssistantRightRegion.setEmpty();
@@ -236,6 +243,21 @@
         return orientationRectF;
     }
 
+    private void updateAssistantRegions(OrientationRectF orientationRectF) {
+        int navbarHeight = getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
+        int assistantWidth = mResources.getDimensionPixelSize(R.dimen.gestures_assistant_width);
+        float assistantHeight = Math.max(navbarHeight, mContractInfo.getWindowCornerRadius());
+        mAssistantLeftRegion.bottom = mAssistantRightRegion.bottom = orientationRectF.bottom;
+        mAssistantLeftRegion.top = mAssistantRightRegion.top =
+                orientationRectF.bottom - assistantHeight;
+
+        mAssistantLeftRegion.left = 0;
+        mAssistantLeftRegion.right = assistantWidth;
+
+        mAssistantRightRegion.right = orientationRectF.right;
+        mAssistantRightRegion.left = orientationRectF.right - assistantWidth;
+    }
+
     boolean touchInAssistantRegion(MotionEvent ev) {
         return mAssistantLeftRegion.contains(ev.getX(), ev.getY())
                 || mAssistantRightRegion.contains(ev.getX(), ev.getY());
@@ -258,7 +280,7 @@
     }
 
     int getCurrentActiveRotation() {
-        return mLastRectRotation;
+        return mActiveTouchRotation;
     }
 
     int getQuickStepStartingRotation() {
@@ -303,8 +325,9 @@
                             Log.d(TestProtocol.NO_SWIPE_TO_HOME, "setting mLastRectTouched");
                         }
                         mLastRectTouched = rect;
-                        mLastRectRotation = rect.mRotation;
-                        if (mEnableMultipleRegions && mCurrentDisplayRotation == mLastRectRotation) {
+                        mActiveTouchRotation = rect.mRotation;
+                        if (mEnableMultipleRegions
+                                && mCurrentDisplayRotation == mActiveTouchRotation) {
                             // TODO(b/154580671) might make this block unnecessary
                             // Start a touch session for the default nav region for the display
                             mQuickStepStartingRotation = mLastRectTouched.mRotation;
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 79b38f2..0a70bd6 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -151,7 +151,7 @@
      * sysui to adjust the navbar.
      */
     private OrientationEventListener mOrientationListener;
-    private int mPreviousRotation = ROTATION_0;
+    private int mSensorRotation = ROTATION_0;
     /**
      * This is the configuration of the foreground app or the app that will be in the foreground
      * once a quickstep gesture finishes.
@@ -246,18 +246,18 @@
             @Override
             public void onOrientationChanged(int degrees) {
                 int newRotation = RecentsOrientedState.getRotationForUserDegreesRotated(degrees,
-                        mPreviousRotation);
-                if (newRotation == mPreviousRotation) {
+                        mSensorRotation);
+                if (newRotation == mSensorRotation) {
                     return;
                 }
 
-                mPreviousRotation = newRotation;
+                mSensorRotation = newRotation;
                 mPrioritizeDeviceRotation = true;
 
                 if (newRotation == mCurrentAppRotation) {
                     // When user rotates device to the orientation of the foreground app after
                     // quickstepping
-                    toggleSecondaryNavBarsForRotation(false);
+                    toggleSecondaryNavBarsForRotation();
                 }
             }
         };
@@ -339,14 +339,16 @@
         mCurrentAppRotation = mDisplayRotation;
 
         /* Update nav bars on the following:
-         * a) if we're not expecting quickswitch, this is coming from an activity rotation
-         * b) we launch an app in the orientation that user is already in
-         * c) We're not in overview, since overview will always be portrait (w/o home rotation)
+         * a) if this is coming from an activity rotation OR
+         *   aa) we launch an app in the orientation that user is already in
+         * b) We're not in overview, since overview will always be portrait (w/o home rotation)
+         * c) We're actively in quickswitch mode
          */
         if ((mPrioritizeDeviceRotation
-                || mCurrentAppRotation == mPreviousRotation) // switch to an app of orientation user is in
-                && !mInOverview) {
-            toggleSecondaryNavBarsForRotation(false);
+                || mCurrentAppRotation == mSensorRotation) // switch to an app of orientation user is in
+                && !mInOverview
+                && mTaskListFrozen) {
+            toggleSecondaryNavBarsForRotation();
         }
     }
 
@@ -461,7 +463,9 @@
      * @return whether SystemUI is in a state where we can start a system gesture.
      */
     public boolean canStartSystemGesture() {
-        return (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0
+        boolean canStartWithNavHidden = (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0
+                || mTaskListFrozen;
+        return canStartWithNavHidden
                 && (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0
                 && (mSystemUiStateFlags & SYSUI_STATE_QUICK_SETTINGS_EXPANDED) == 0
                 && ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0
@@ -634,29 +638,26 @@
     }
 
     private void enableMultipleRegions(boolean enable) {
-        toggleSecondaryNavBarsForRotation(enable);
-        if (enable && !TestProtocol.sDisableSensorRotation) {
+        mOrientationTouchTransformer.enableMultipleRegions(enable, mDefaultDisplay.getInfo());
+        notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getQuickStepStartingRotation());
+        if (enable && !mInOverview && !TestProtocol.sDisableSensorRotation) {
+            // Clear any previous state from sensor manager
+            mSensorRotation = mCurrentAppRotation;
             mOrientationListener.enable();
         } else {
             mOrientationListener.disable();
         }
     }
 
-    private void notifySysuiForRotation(int rotation) {
-        UI_HELPER_EXECUTOR.execute(() ->
-                SystemUiProxy.INSTANCE.get(mContext).onQuickSwitchToNewTask(rotation));
-    }
-
     public void onStartGesture() {
         if (mTaskListFrozen) {
             // Prioritize whatever nav bar user touches once in quickstep
             // This case is specifically when user changes what nav bar they are using mid
             // quickswitch session before tasks list is unfrozen
-            notifySysuiForRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
+            notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
         }
     }
 
-
     void onEndTargetCalculated(GestureState.GestureEndTarget endTarget,
             BaseActivityInterface activityInterface) {
         if (endTarget == GestureState.GestureEndTarget.RECENTS) {
@@ -673,7 +674,8 @@
                 // First gesture to start quickswitch
                 enableMultipleRegions(true);
             } else {
-                notifySysuiForRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
+                notifySysuiOfCurrentRotation(
+                        mOrientationTouchTransformer.getCurrentActiveRotation());
             }
 
             // A new gesture is starting, reset the current device rotation
@@ -685,7 +687,7 @@
                 // touched nav bar but didn't go anywhere and not quickswitching, do nothing
                 return;
             }
-            notifySysuiForRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
+            notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
         }
     }
 
@@ -697,13 +699,10 @@
     /**
      * Disables/Enables multiple nav bars on {@link OrientationTouchTransformer} and then
      * notifies system UI of the primary rotation the user is interacting with
-     *
-     * @param enable if {@code true}, this will report to sysUI the navbar of the region the gesture
-     *               started in (during ACTION_DOWN), otherwise will report {@param displayRotation}
      */
-    private void toggleSecondaryNavBarsForRotation(boolean enable) {
-        mOrientationTouchTransformer.enableMultipleRegions(enable, mDefaultDisplay.getInfo());
-        notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getQuickStepStartingRotation());
+    private void toggleSecondaryNavBarsForRotation() {
+        mOrientationTouchTransformer.setSingleActiveRegion(mDefaultDisplay.getInfo());
+        notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
     }
 
     public int getCurrentActiveRotation() {
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 4f53d45..5c4a492 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -138,9 +138,6 @@
     public static final BooleanFlag ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER = getDebugFlag(
             "ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER", true, "Show launcher preview in grid picker");
 
-    public static final BooleanFlag USE_SURFACE_VIEW_FOR_GRID_PREVIEW = getDebugFlag(
-            "USE_SURFACE_VIEW_FOR_GRID_PREVIEW", true, "Use surface view for grid preview");
-
     public static final BooleanFlag ENABLE_OVERVIEW_ACTIONS = getDebugFlag(
             "ENABLE_OVERVIEW_ACTIONS", true, "Show app actions instead of the shelf in Overview."
             + " As part of this decoupling, also distinguish swipe up from nav bar vs above it.");
diff --git a/src/com/android/launcher3/graphics/GridOptionsProvider.java b/src/com/android/launcher3/graphics/GridOptionsProvider.java
index 9bfd5ba..08d7e4c 100644
--- a/src/com/android/launcher3/graphics/GridOptionsProvider.java
+++ b/src/com/android/launcher3/graphics/GridOptionsProvider.java
@@ -1,21 +1,14 @@
 package com.android.launcher3.graphics;
 
-import static com.android.launcher3.config.FeatureFlags.USE_SURFACE_VIEW_FOR_GRID_PREVIEW;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-
 import android.content.ContentProvider;
 import android.content.ContentValues;
 import android.content.pm.PackageManager;
 import android.content.res.XmlResourceParser;
 import android.database.Cursor;
 import android.database.MatrixCursor;
-import android.graphics.Bitmap;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
-import android.os.ParcelFileDescriptor;
-import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
-import android.text.TextUtils;
 import android.util.Log;
 import android.util.Xml;
 
@@ -26,12 +19,10 @@
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
-import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.concurrent.Future;
 
 /**
  * Exposes various launcher grid options and allows the caller to change them.
@@ -62,24 +53,7 @@
     private static final String KEY_LIST_OPTIONS = "/list_options";
     private static final String KEY_DEFAULT_GRID = "/default_grid";
 
-    private static final String KEY_PREVIEW = "preview";
-    private static final String MIME_TYPE_PNG = "image/png";
-
     private static final String METHOD_GET_PREVIEW = "get_preview";
-    private static final String METADATA_KEY_PREVIEW_VERSION = "preview_version";
-
-    public static final PipeDataWriter<Future<Bitmap>> BITMAP_WRITER =
-            new PipeDataWriter<Future<Bitmap>>() {
-                @Override
-                public void writeDataToPipe(ParcelFileDescriptor output, Uri uri, String s,
-                        Bundle bundle, Future<Bitmap> bitmap) {
-                    try (AutoCloseOutputStream os = new AutoCloseOutputStream(output)) {
-                        bitmap.get().compress(Bitmap.CompressFormat.PNG, 100, os);
-                    } catch (Exception e) {
-                        Log.w(TAG, "fail to write to pipe", e);
-                    }
-                }
-            };
 
     @Override
     public boolean onCreate() {
@@ -104,10 +78,6 @@
                     .add(KEY_IS_DEFAULT, idp.numColumns == gridOption.numColumns
                             && idp.numRows == gridOption.numRows);
         }
-        Bundle metadata = new Bundle();
-        metadata.putString(METADATA_KEY_PREVIEW_VERSION,
-                USE_SURFACE_VIEW_FOR_GRID_PREVIEW.get() ? "V2" : "V1");
-        cursor.setExtras(metadata);
         return cursor;
     }
 
@@ -132,10 +102,6 @@
 
     @Override
     public String getType(Uri uri) {
-        List<String> segments = uri.getPathSegments();
-        if (segments.size() > 0 && KEY_PREVIEW.equals(segments.get(0))) {
-            return MIME_TYPE_PNG;
-        }
         return "vnd.android.cursor.dir/launcher_grid";
     }
 
@@ -173,33 +139,6 @@
     }
 
     @Override
-    public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
-        List<String> segments = uri.getPathSegments();
-        if (segments.size() < 2 || !KEY_PREVIEW.equals(segments.get(0))) {
-            throw new FileNotFoundException("Invalid preview url");
-        }
-        String profileName = segments.get(1);
-        if (TextUtils.isEmpty(profileName)) {
-            throw new FileNotFoundException("Invalid preview url");
-        }
-
-        InvariantDeviceProfile idp;
-        try {
-            idp = new InvariantDeviceProfile(getContext(), profileName);
-        } catch (Exception e) {
-            throw new FileNotFoundException(e.getMessage());
-        }
-
-        try {
-            return openPipeHelper(uri, MIME_TYPE_PNG, null,
-                    UI_HELPER_EXECUTOR.submit(new LauncherPreviewRenderer(getContext(), idp)),
-                    BITMAP_WRITER);
-        } catch (Exception e) {
-            throw new FileNotFoundException(e.getMessage());
-        }
-    }
-
-    @Override
     public Bundle call(String method, String arg, Bundle extras) {
         if (getContext().checkPermission("android.permission.BIND_WALLPAPER",
                 Binder.getCallingPid(), Binder.getCallingUid())
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 15f4e3f..885fb66 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -20,7 +20,6 @@
 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.ModelUtils.filterCurrentWorkspaceItems;
 import static com.android.launcher3.model.ModelUtils.getMissingHotseatRanks;
 import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially;
@@ -34,8 +33,6 @@
 import android.content.Intent;
 import android.content.pm.ShortcutInfo;
 import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.drawable.AdaptiveIconDrawable;
@@ -63,20 +60,16 @@
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.WorkspaceLayoutManager;
 import com.android.launcher3.allapps.SearchUiManager;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.folder.FolderIcon;
 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;
@@ -105,7 +98,6 @@
 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;
 import java.util.concurrent.FutureTask;
@@ -121,7 +113,7 @@
  *   4) Measure and draw the view on a canvas
  */
 @TargetApi(Build.VERSION_CODES.O)
-public class LauncherPreviewRenderer implements Callable<Bitmap> {
+public class LauncherPreviewRenderer {
 
     private static final String TAG = "LauncherPreviewRenderer";
 
@@ -213,15 +205,17 @@
     private final Context mContext;
     private final InvariantDeviceProfile mIdp;
     private final DeviceProfile mDp;
+    private final boolean mMigrated;
     private final Rect mInsets;
 
     private final WorkspaceItemInfo mWorkspaceItemInfo;
 
-    public LauncherPreviewRenderer(Context context, InvariantDeviceProfile idp) {
+    public LauncherPreviewRenderer(Context context, InvariantDeviceProfile idp, boolean migrated) {
         mUiHandler = new Handler(Looper.getMainLooper());
         mContext = context;
         mIdp = idp;
         mDp = idp.portraitProfile.copy(context);
+        mMigrated = migrated;
 
         // TODO: get correct insets once display cutout API is available.
         mInsets = new Rect();
@@ -243,28 +237,6 @@
                 context.getString(R.string.label_application);
     }
 
-    @Override
-    public Bitmap call() {
-        return BitmapRenderer.createHardwareBitmap(mDp.widthPx, mDp.heightPx, c -> {
-
-            if (Looper.myLooper() == Looper.getMainLooper()) {
-                new MainThreadRenderer(mContext).renderScreenShot(c);
-            } else {
-                CountDownLatch latch = new CountDownLatch(1);
-                Utilities.postAsyncCallback(mUiHandler, () -> {
-                    new MainThreadRenderer(mContext).renderScreenShot(c);
-                    latch.countDown();
-                });
-
-                try {
-                    latch.await();
-                } catch (Exception e) {
-                    Log.e(TAG, "Error drawing on main thread", e);
-                }
-            }
-        });
-    }
-
     /** Populate preview and render it. */
     public View getRenderedView() {
         MainThreadRenderer renderer = new MainThreadRenderer(mContext);
@@ -407,20 +379,9 @@
 
         private void populate() {
             if (ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER.get()) {
-                boolean needsToMigrate =
-                        MULTI_DB_GRID_MIRATION_ALGO.get()
-                                ? GridSizeMigrationTaskV2.needsToMigrate(mContext, mIdp)
-                                : GridSizeMigrationTask.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;
                 PreviewContext previewContext = null;
-                if (needsToMigrate && success) {
+                if (mMigrated) {
                     previewContext = new PreviewContext(mContext, mIdp);
                     LauncherAppState appForPreview = new LauncherAppState(
                             previewContext, null /* iconCacheFileName */);
@@ -535,12 +496,6 @@
             // Additional measure for views which use auto text size API
             measureView(mRootView, mDp.widthPx, mDp.heightPx);
         }
-
-        private void renderScreenShot(Canvas canvas) {
-            populate();
-            mRootView.draw(canvas);
-            dispatchVisibilityAggregated(mRootView, false);
-        }
     }
 
     private static void measureView(View view, int width, int height) {
diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
index 350f221..fdc3a94 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -16,7 +16,9 @@
 
 package com.android.launcher3.graphics;
 
+import static com.android.launcher3.config.FeatureFlags.MULTI_DB_GRID_MIRATION_ALGO;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
 import android.content.Context;
 import android.hardware.display.DisplayManager;
@@ -32,6 +34,8 @@
 import android.view.animation.AccelerateDecelerateInterpolator;
 
 import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.model.GridSizeMigrationTask;
+import com.android.launcher3.model.GridSizeMigrationTaskV2;
 
 import java.util.concurrent.TimeUnit;
 
@@ -93,32 +97,35 @@
             return null;
         }
 
-        MAIN_EXECUTOR.execute(() -> {
-            // If mSurfaceControlViewHost is null due to any reason (e.g. binder died,
-            // happening when user leaves the preview screen before preview rendering finishes),
-            // we should return here.
-            SurfaceControlViewHost host = mSurfaceControlViewHost;
-            if (host == null) {
-                return;
-            }
+        MODEL_EXECUTOR.post(() -> {
+            final boolean success = doGridMigrationIfNecessary();
 
-            View view = new LauncherPreviewRenderer(mContext, mIdp).getRenderedView();
-            // This aspect scales the view to fit in the surface and centers it
-            final float scale = Math.min(mWidth / (float) view.getMeasuredWidth(),
-                    mHeight / (float) view.getMeasuredHeight());
-            view.setScaleX(scale);
-            view.setScaleY(scale);
-            view.setPivotX(0);
-            view.setPivotY(0);
-            view.setTranslationX((mWidth - scale * view.getWidth()) / 2);
-            view.setTranslationY((mHeight - scale * view.getHeight()) / 2);
-            view.setAlpha(0);
-            view.animate().alpha(1)
-                    .setInterpolator(new AccelerateDecelerateInterpolator())
-                    .setDuration(FADE_IN_ANIMATION_DURATION)
-                    .start();
-            host.setView(view, view.getMeasuredWidth(),
-                    view.getMeasuredHeight());
+            MAIN_EXECUTOR.post(() -> {
+                // If mSurfaceControlViewHost is null due to any reason (e.g. binder died,
+                // happening when user leaves the preview screen before preview rendering finishes),
+                // we should return here.
+                SurfaceControlViewHost host = mSurfaceControlViewHost;
+                if (host == null) {
+                    return;
+                }
+
+                View view = new LauncherPreviewRenderer(mContext, mIdp, success).getRenderedView();
+                // This aspect scales the view to fit in the surface and centers it
+                final float scale = Math.min(mWidth / (float) view.getMeasuredWidth(),
+                        mHeight / (float) view.getMeasuredHeight());
+                view.setScaleX(scale);
+                view.setScaleY(scale);
+                view.setPivotX(0);
+                view.setPivotY(0);
+                view.setTranslationX((mWidth - scale * view.getWidth()) / 2);
+                view.setTranslationY((mHeight - scale * view.getHeight()) / 2);
+                view.setAlpha(0);
+                view.animate().alpha(1)
+                        .setInterpolator(new AccelerateDecelerateInterpolator())
+                        .setDuration(FADE_IN_ANIMATION_DURATION)
+                        .start();
+                host.setView(view, view.getMeasuredWidth(), view.getMeasuredHeight());
+            });
         });
 
         Bundle result = new Bundle();
@@ -145,4 +152,17 @@
         }
         mHostToken.unlinkToDeath(this, 0);
     }
+
+    private boolean doGridMigrationIfNecessary() {
+        boolean needsToMigrate =
+                MULTI_DB_GRID_MIRATION_ALGO.get()
+                        ? GridSizeMigrationTaskV2.needsToMigrate(mContext, mIdp)
+                        : GridSizeMigrationTask.needsToMigrate(mContext, mIdp);
+        if (!needsToMigrate) {
+            return false;
+        }
+        return MULTI_DB_GRID_MIRATION_ALGO.get()
+                ? GridSizeMigrationTaskV2.migrateGridIfNeeded(mContext, mIdp)
+                : GridSizeMigrationTask.migrateGridIfNeeded(mContext, mIdp);
+    }
 }
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
index 25a2c69..ebdfa8c 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
@@ -248,10 +248,10 @@
     /** Return what's in the src but not in the dest */
     private static List<DbEntry> calcDiff(List<DbEntry> src, List<DbEntry> dest) {
         Set<String> destIntentSet = new HashSet<>();
-        Set<Set<String>> destFolderIntentSet = new HashSet<>();
+        Set<Map<String, Integer>> destFolderIntentSet = new HashSet<>();
         for (DbEntry entry : dest) {
             if (entry.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
-                destFolderIntentSet.add(entry.mFolderItems.keySet());
+                destFolderIntentSet.add(getFolderIntents(entry));
             } else {
                 destIntentSet.add(entry.mIntent);
             }
@@ -259,7 +259,7 @@
         List<DbEntry> diff = new ArrayList<>();
         for (DbEntry entry : src) {
             if (entry.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
-                if (!destFolderIntentSet.contains(entry.mFolderItems.keySet())) {
+                if (!destFolderIntentSet.contains(getFolderIntents(entry))) {
                     diff.add(entry);
                 }
             } else {
@@ -271,13 +271,23 @@
         return diff;
     }
 
+    private static Map<String, Integer> getFolderIntents(DbEntry entry) {
+        Map<String, Integer> folder = new HashMap<>();
+        for (String intent : entry.mFolderItems.keySet()) {
+            folder.put(intent, entry.mFolderItems.get(intent).size());
+        }
+        return folder;
+    }
+
     private static void insertEntryInDb(SQLiteDatabase db, Context context, DbEntry entry,
             String srcTableName, String destTableName) {
         int id = copyEntryAndUpdate(db, context, entry, srcTableName, destTableName);
 
         if (entry.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
-            for (int itemId : entry.mFolderItems.values()) {
-                copyEntryAndUpdate(db, context, itemId, id, srcTableName, destTableName);
+            for (Set<Integer> itemIds : entry.mFolderItems.values()) {
+                for (int itemId : itemIds) {
+                    copyEntryAndUpdate(db, context, itemId, id, srcTableName, destTableName);
+                }
             }
         }
     }
@@ -675,7 +685,10 @@
                     String intent = c.getString(1);
                     verifyIntent(intent);
                     total++;
-                    entry.mFolderItems.put(intent, id);
+                    if (!entry.mFolderItems.containsKey(intent)) {
+                        entry.mFolderItems.put(intent, new HashSet<>());
+                    }
+                    entry.mFolderItems.get(intent).add(id);
                 } catch (Exception e) {
                     removeEntryFromDb(mDb, mTableName, IntArray.wrap(c.getInt(0)));
                 }
@@ -714,7 +727,7 @@
 
         private String mIntent;
         private String mProvider;
-        private Map<String, Integer> mFolderItems = new HashMap<>();
+        private Map<String, Set<Integer>> mFolderItems = new HashMap<>();
 
         /** Comparator according to the reading order */
         @Override
@@ -748,9 +761,5 @@
             values.put(LauncherSettings.Favorites.SPANX, spanX);
             values.put(LauncherSettings.Favorites.SPANY, spanY);
         }
-
-        public String getIntentStr() {
-            return mIntent;
-        }
     }
 }
diff --git a/src/com/android/launcher3/model/data/FolderInfo.java b/src/com/android/launcher3/model/data/FolderInfo.java
index 05ce06a..41ccbd7 100644
--- a/src/com/android/launcher3/model/data/FolderInfo.java
+++ b/src/com/android/launcher3/model/data/FolderInfo.java
@@ -226,7 +226,7 @@
         }
 
         // Updating title to same value does not change any states.
-        if (title != null && title == this.title) {
+        if (title != null && title.equals(this.title)) {
             return;
         }
 
@@ -236,7 +236,15 @@
                         : title.length() == 0 ? LabelState.EMPTY :
                                 getAcceptedSuggestionIndex().isPresent() ? LabelState.SUGGESTED
                                         : LabelState.MANUAL;
-        setOption(FLAG_MANUAL_FOLDER_NAME, newLabelState.equals(LabelState.MANUAL), modelWriter);
+
+        if (newLabelState.equals(LabelState.MANUAL)) {
+            options |= FLAG_MANUAL_FOLDER_NAME;
+        } else {
+            options &= ~FLAG_MANUAL_FOLDER_NAME;
+        }
+        if (modelWriter != null) {
+            modelWriter.updateItemInDatabase(this);
+        }
     }
 
     /**
diff --git a/src/com/android/launcher3/util/DefaultDisplay.java b/src/com/android/launcher3/util/DefaultDisplay.java
index 150fb5b..35788a5 100644
--- a/src/com/android/launcher3/util/DefaultDisplay.java
+++ b/src/com/android/launcher3/util/DefaultDisplay.java
@@ -49,20 +49,25 @@
 
     public static final int CHANGE_ALL = CHANGE_SIZE | CHANGE_ROTATION | CHANGE_FRAME_DELAY;
 
-    private final Context mContext;
+    private final Context mDisplayContext;
     private final int mId;
     private final ArrayList<DisplayInfoChangeListener> mListeners = new ArrayList<>();
     private final Handler mChangeHandler;
     private Info mInfo;
 
     private DefaultDisplay(Context context) {
-        mContext = context;
-        mInfo = new Info(context);
+        DisplayManager dm = context.getSystemService(DisplayManager.class);
+        // Use application context to create display context so that it can have its own Resources.
+        mDisplayContext = context.getApplicationContext().createDisplayContext(
+                dm.getDisplay(DEFAULT_DISPLAY));
+        // Note that the Display object must be obtained from DisplayManager which is associated to
+        // the display context, so the Display is isolated from Activity and Application to provide
+        // the actual state of device that excludes the additional adjustment and override.
+        mInfo = new Info(mDisplayContext);
         mId = mInfo.id;
         mChangeHandler = new Handler(this::onChange);
 
-        context.getSystemService(DisplayManager.class)
-                .registerDisplayListener(this, UI_HELPER_EXECUTOR.getHandler());
+        dm.registerDisplayListener(this, UI_HELPER_EXECUTOR.getHandler());
     }
 
     @Override
@@ -78,7 +83,7 @@
         }
 
         Info oldInfo = mInfo;
-        Info info = new Info(mContext);
+        Info info = new Info(mDisplayContext);
 
         int change = 0;
         if (info.hasDifferentSize(oldInfo)) {
@@ -162,8 +167,7 @@
             display.getRealSize(realSize);
             display.getCurrentSizeRange(smallestSize, largestSize);
 
-            Context defaultDisplayContext = context.createDisplayContext(display);
-            metrics = defaultDisplayContext.getResources().getDisplayMetrics();
+            metrics = context.getResources().getDisplayMetrics();
         }
 
         private boolean hasDifferentSize(Info info) {